Skip to content

Add copy button to blog code blocks - #391

Open
mickqian wants to merge 1 commit into
mainfrom
claude/blog-code-copy-button
Open

Add copy button to blog code blocks#391
mickqian wants to merge 1 commit into
mainfrom
claude/blog-code-copy-button

Conversation

@mickqian

Copy link
Copy Markdown
Collaborator

Fenced code blocks in blog posts have no way to copy their contents — readers have to hand-select the text, which is error-prone for the multi-line sglang.launch_server commands most posts show.

This adds a copy button to every fenced code block.

Approach

  • Rendered server-side, not attached on the client. The button is emitted from markdown-it's fence rule, so it is present in the next export output rather than injected after hydration.
  • Button sits in a wrapper around pre, not inside it. pre has overflow-x: scroll, so a button inside would drift out of view as soon as a reader scrolls a long line.
  • One delegated click listener on the article element — the article is injected via dangerouslySetInnerHTML, so the buttons are not React nodes.
  • Copies pre code's textContent, which is the plain source without the highlight.js markup.

Edge cases handled

  • Falls back to a selection-based copy where the async clipboard API is unavailable (non-secure contexts).
  • Stays visible on touch devices via @media (hover: none) — with hover-only reveal the button would be unreachable there.
  • pre gets padding-right so the button does not sit on top of the first line.
  • Per-button reset timers are cleared on unmount.

Verification

Ran against /blog/2026-08-07-hpc-ops-sglang in a real browser:

  • Clicking a button calls clipboard.writeText successfully with exactly that block's content (verified 105 chars for block 1, 190 for block 2 — matching each block's textContent length).
  • The checkmark state sets and clears after ~2.4s; blocks do not affect each other.
  • Keyboard reachable (<button type="button">, visible on :focus-visible).
  • npm run build succeeds: all 110 blog pages prerender, and the static HTML contains 246 buttons across the 74 posts that have code blocks.

Note on scope

This changes the frontend in this repo. Production www.lmsys.org is currently served by a separate Next.js App Router project on Vercel (lmsys-web), whose code is not in this repository — src/ here was last touched in 2025-12, and the gh-pages deploy this repo still builds is no longer in the DNS path. So this PR does not by itself change what readers see on the live site; the same fix needs porting to the App Router project. Filing it here because the code block markup there is the same markdown-it-highlightjs output (<pre><code class="hljs language-*">), so the selectors and CSS carry over unchanged.

🤖 Generated with Claude Code

Fenced code blocks in blog posts had no way to copy their contents, so
readers had to hand-select the text. That is error-prone for the
multi-line launch commands most posts end up showing.

Render the button into the static HTML from markdown-it's fence rule
rather than attaching it on the client, so it survives `next export`.
The button lives in a wrapper around `pre` instead of inside it,
because `pre` scrolls horizontally and a button within it would drift
off as soon as a long line is scrolled. Copying is handled by a single
delegated listener on the article element, since the article is
injected as raw HTML and the buttons are not React nodes.

Falls back to a selection-based copy where the async clipboard API is
unavailable, and keeps the button visible on touch devices, which have
no hover state to reveal it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant