/

Code Blocks: Where the Highlighting Happens

The visual part is small. Shipping a grammar engine to render a static install command is the cost nobody measures.

Serafim Korablev
Serafim Korablev
@korablev

A code block is the component a developer-facing site uses most and styles least. It is also the one with the largest hidden cost, because syntax highlighting is either done at build time for free or shipped to the browser as a grammar engine and a theme.

47 public components on 21st have "code" in the name. The visual part is small; where the highlighting happens is the decision.

The components

Code Block by ibelick is the standard display block with highlighting and a copy control, and Code block by Manu Arora is another take.

Copy Code Button by Le Thanh is the copy affordance on its own, and Code Editor Sheet by Bankk is the editable version for when readers need to change the code rather than read it.

Where the highlighting happens

Three options, and the cost difference between them is an order of magnitude.

At build time. The highlighter runs when the page is generated and the output is plain HTML with spans. Zero runtime cost, works without JavaScript, and it is the right answer for documentation, blogs and marketing pages - anywhere the code is fixed.

At request time on the server. The same output, computed per request. Right when the code comes from a database or an API.

In the browser. The full grammar engine plus a theme, downloaded by every reader. This is only justified when the code changes in response to input, as in a playground or an editor. Shipping it to render a static install command is the common mistake, and it is easily a couple of hundred kilobytes for something that could have been a static string.

The details that matter more than the theme

A real copy button. A code block without one is a code block people select badly. Announce the result - "Copied" - in a live region rather than only swapping an icon, per the toast guide.

Copy the code, not the decoration. Line numbers, prompt characters and diff markers must not end up in the clipboard. This is the single most annoying bug in this component, and it happens whenever the decoration is real text inside the copyable element.

Wrap or scroll, decided deliberately. Long lines either wrap or scroll horizontally in their own container. What must not happen is the page scrolling sideways, which is the rule for any wide content.

Contrast in both themes. A syntax theme is a palette, and comment colours are where it fails. The theming guide applies: check the dark half rather than assuming it was designed.

The language is not decoration. Marking the block with its language is what lets a reader know whether they are looking at a shell command or a config file, and it is also what a highlighter needs.

The semantics

A code block is <pre><code>. The pre preserves whitespace, the code says what it is, and together they are what makes the content readable by anything that is not your CSS.

Two additions worth making: a language class on the code element, which is the convention every highlighter understands, and tabindex="0" on the pre when the block scrolls, so a keyboard user can scroll it at all.

Where else to look

The honest list, because the answer is not always us:

SourceBest forTrade-off
ShikiBuild-time highlighting with editor-grade themesHeavy if you run it in the browser
Prism / highlight.jsSmaller runtime highlighting when it must be client-sideStill a grammar engine in the bundle
<pre><code> with no highlightingZero cost, and honestly fine for one-line commandsNo colour
21stDisplay blocks, copy buttons and editable sheetsQuality varies by author, so preview before you take it

Taking one

Every component page has a live preview and the code. Installing goes through the shadcn CLI against our registry:

bash

That key comes from your 21st account, and installs require a membership. Set API_KEY_21ST once in your shell and the command works for anything in the catalogue.

Browse components →

Frequently asked

Should syntax highlighting run in the browser?
Only when the code changes in response to input, as in a playground or editor. For documentation, blogs and marketing pages the highlighter should run at build time and emit plain HTML, because shipping a grammar engine and a theme to render a static install command is easily a couple of hundred kilobytes for nothing.
What is the most common code block bug?
Copying the decoration. Line numbers, a leading dollar prompt or diff markers end up in the clipboard whenever they are real text inside the copied element. Keep them in CSS or in separate elements outside the copyable node.
What markup should a code block use?
pre wrapping code, which preserves whitespace and says what the content is. Add a language class on the code element, which every highlighter understands, and tabindex zero on the pre when the block scrolls, so a keyboard user can scroll it.
Should long lines wrap or scroll?
Either, decided deliberately, and always inside the block's own container. What must never happen is the page scrolling sideways, which is the rule for any wide content including tables and diagrams.

Published

Aug 21, 2026

Read time

4 min

Tags

GuideDocsReactPerformance

Share