
How to Style Code Snippets and Technical Typography?
- Sajjad
- Typography
- 01 Oct, 2026
You publish a tutorial, open it on your phone, and the first code block runs off the right edge of the screen. The inline npm install command in the paragraph above it is rendered in a monospace font that looks twice as big as the surrounding text, the comment color is so pale it is nearly invisible, and the copy button covers the last few characters of the longest line. None of these are exotic bugs. They are the default outcome when code is treated as an afterthought in a site's typography.
This article walks through how to style technical content properly: inline code, multi-line code blocks, keyboard shortcuts, file paths, and terminal output. You will get working CSS for sizing monospace type relative to body text, handling overflow, building accessible syntax highlighting colors, adding line numbers and highlighted lines, and supporting dark mode, along with the HTML semantics that make it all work for screen readers.
What Counts as Technical Typography?
Technical typography is the set of styles you apply to text that represents code, commands, data, or user input rather than prose. HTML gives you a small but precise vocabulary for it, and using the right element is the first styling decision:
| Element | Meaning | Typical example |
|---|---|---|
code | A fragment of computer code | display: grid, useEffect |
pre | Preformatted text, whitespace preserved | Multi-line code blocks |
kbd | User input, usually keyboard keys | Ctrl + C, Cmd + Shift + P |
samp | Sample output from a program | Error: ENOENT: no such file |
var | A placeholder variable | A filename the reader should replace |
A code block is a pre element wrapping a code element. The pre preserves whitespace and line breaks; the code says what the content is. Markdown renderers, including the MDX pipeline this site uses, produce exactly that structure from fenced code blocks, usually adding a language-css or similar class to the code element.
The goal of the styling is simple: code should be instantly distinguishable from prose, readable at a glance, and never break the layout.
Choose the Right Monospace Font
Code needs a monospace font because alignment, indentation, and character-by-character accuracy matter. The detailed comparison of fonts is covered in how to pick a monospace font for code, so here is just the practical stack:
:root {
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono",
Menlo, Consolas, "Liberation Mono", monospace;
}
ui-monospace maps to SF Mono on Apple platforms. Cascadia Code ships with recent Windows versions, Consolas with older ones, and Menlo with macOS. Ending with the generic monospace keyword guarantees a fallback.
If you want a consistent look everywhere, self-host one web font such as JetBrains Mono or IBM Plex Mono and put it first. Keep the character set small: code blocks rarely need more than Basic Latin plus a handful of punctuation, so a subsetted file can be under 25 KB.
What to check in any code font:
- Distinguishable confusables:
0vsO,1vslvsI, andrnvsmmust be unambiguous. - A large enough x-height to stay readable at small sizes.
- Clear punctuation: braces, brackets, semicolons, and backticks should be easy to tell apart.
Size Monospace Text Correctly
The most common bug in technical typography is mismatched size. Browsers have a quirk: when an element's font family is only the generic monospace keyword, many browsers shrink the default size from 16px to 13px. Developers then compensate with hard-coded pixel values, and inline code ends up either too small or too large relative to the paragraph.
Fix the quirk first, then size code relative to its context:
code,
kbd,
samp,
pre {
font-family: var(--font-mono);
font-size: 1em; /* reset the legacy monospace shrink */
}
/* Inline code sits inside a sentence */
:not(pre) > code {
font-size: 0.875em;
}
/* Block code gets its own size */
pre {
font-size: 0.875rem;
line-height: 1.6;
}
Using em for inline code means it scales with whatever text it sits in, whether that is a paragraph, a heading, or a table cell. Monospace fonts also tend to look larger than proportional fonts at the same nominal size because their glyphs are wider, so 0.875em to 0.9em usually reads as "the same size" visually.
If your mono font has a noticeably different x-height from your body font, font-size-adjust is a cleaner tool than guessing:
:not(pre) > code {
font-size-adjust: ex-height 0.5;
}
font-size-adjust is supported in all current major browsers. It scales the fallback or secondary font so its x-height matches the ratio you specify, which keeps inline code visually aligned with surrounding prose. Pair this with rem for font sizes at the block level so code still respects user font preferences.
Style Inline Code
Inline code needs just enough contrast to stand out without shouting. A subtle background and a little horizontal padding is the standard pattern:
:not(pre) > code {
font-size: 0.875em;
padding: 0.125em 0.375em;
border-radius: 0.25rem;
background-color: var(--color-code-bg);
color: var(--color-code-text);
overflow-wrap: anywhere;
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
}
A few details are doing real work here:
- Vertical padding in
emkeeps the background from colliding with lines above and below at different sizes. overflow-wrap: anywherelets a long token like a URL or a package name break instead of overflowing on narrow screens. Without it, a single@scope/very-long-package-namecan push the page sideways.box-decoration-break: clonegives each line fragment its own padding and rounded corners when inline code wraps, instead of a background that looks sliced off.
Avoid coloring inline code in a bright accent color that matches your links. Readers will try to click it. Keep links and code visually distinct, and if code appears inside a link, let the link styles win:
a code {
color: inherit;
}
Style Code Blocks
Code blocks are where layout breaks happen. The base styles below handle overflow, spacing, and tab width:
pre {
margin-block: 1.5em;
padding: 1rem 1.25rem;
border-radius: 0.5rem;
background-color: var(--color-pre-bg);
color: var(--color-pre-text);
overflow-x: auto;
tab-size: 2;
white-space: pre;
-webkit-text-size-adjust: 100%;
}
pre code {
padding: 0;
background: none;
font-size: inherit;
color: inherit;
}
Scroll or Wrap?
You have two choices for long lines:
- Horizontal scroll (
white-space: prewithoverflow-x: auto). This preserves the exact formatting, which matters for languages where indentation is meaningful, such as Python and YAML. It is the right default. - Soft wrap (
white-space: pre-wrap). This avoids scrolling but can make indentation misleading. It works well for shell commands and prose-like output such as logs.
A sensible compromise is scroll by default and wrap for terminal snippets:
pre:has(> code.language-bash),
pre:has(> code.language-text) {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
Make Scrollable Blocks Keyboard-Accessible
A scrolling region that cannot be focused cannot be scrolled with the keyboard. Make the pre focusable and give it a visible focus ring:
<pre tabindex="0"><code class="language-css">/* ... */</code></pre>
pre:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
Chrome and Firefox now make scroll containers keyboard-focusable automatically, but adding tabindex="0" covers Safari and older versions. If you can't edit the generated markup, add the attribute in a small rehype plugin or a client component.
Keep Line Length Reasonable
Body text works best at 45 to 75 characters per line, but code is different: readers scan code rather than read it, and wrapping changes its meaning. Aim for code blocks that show around 80 characters without scrolling on desktop. At 14px with a typical mono font, that is roughly 680px wide, which fits comfortably in most article columns. If your content column is narrower, let code blocks break out slightly:
.prose pre {
margin-inline: -1.25rem;
}
@media (width < 40rem) {
.prose pre {
margin-inline: -1rem;
border-radius: 0;
}
}
Build Accessible Syntax Highlighting Colors
Syntax highlighting themes are often designed for editors with large monitors and dark rooms, not for article pages. The single most common accessibility failure is low-contrast comments.
WCAG 1.4.3 requires 4.5:1 contrast for normal text against its background, and code is normal text. That applies to every token color, including comments, punctuation, and line numbers if they carry meaning. Many popular themes ship comment colors around 3:1.
Define your token colors as custom properties and check each against the block background:
:root {
--color-pre-bg: #f6f8fa;
--color-pre-text: #1f2328;
--code-keyword: #a2195e; /* 6.9:1 on #f6f8fa */
--code-string: #0a6640; /* 6.6:1 */
--code-function: #6639ba; /* 7.0:1 */
--code-number: #0550ae; /* 7.0:1 */
--code-comment: #57606a; /* 5.9:1 */
}
.token.keyword { color: var(--code-keyword); }
.token.string { color: var(--code-string); }
.token.function { color: var(--code-function); }
.token.number { color: var(--code-number); }
.token.comment {
color: var(--code-comment);
font-style: italic;
}
Class names vary by highlighter. Prism uses .token.keyword, highlight.js uses .hljs-keyword, and Shiki outputs inline styles or CSS variables depending on configuration. The principle is the same.
Don't rely on color alone to convey meaning. Highlighting is a convenience, not information, so that is usually fine, but for diff blocks, where red and green indicate removed and added lines, also include the + and - markers or an icon.
Server-Side Highlighting
For a Next.js site, highlight at build time rather than shipping a highlighter to the browser. Shiki through rehype-pretty-code produces static HTML with no client JavaScript:
import { MDXRemote } from "next-mdx-remote/rsc";
import rehypePrettyCode from "rehype-pretty-code";
import remarkGfm from "remark-gfm";
export default function MDXContent({ content }: { content: string }) {
return (
<MDXRemote
source={content}
options={{
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
[
rehypePrettyCode,
{ theme: { light: "github-light", dark: "github-dark" } },
],
],
},
}}
/>
);
}
With a dual theme, Shiki emits CSS variables for both palettes, and you switch between them with a few lines of CSS tied to your dark mode selector.
Add Line Numbers and Highlighted Lines
Line numbers help when the surrounding text refers to specific lines. CSS counters handle them without polluting the copyable text:
pre code {
counter-reset: line;
}
pre code .line::before {
counter-increment: line;
content: counter(line);
display: inline-block;
width: 2ch;
margin-right: 1.5ch;
text-align: right;
color: var(--code-comment);
user-select: none;
}
pre code .line[data-highlighted-line] {
background-color: var(--color-line-highlight);
box-shadow: inset 3px 0 0 var(--color-accent);
}
Because the numbers come from a pseudo-element with user-select: none, a reader who selects and copies the code gets clean source without numbers. The inset box-shadow marks highlighted lines with a left bar that doesn't shift the content.
Use line numbers sparingly. A five-line snippet does not need them, and they consume horizontal space on mobile.
Style Keyboard Keys, Paths, and Output
Technical writing is more than code. Keyboard shortcuts, file paths, and terminal output each benefit from distinct styling.
Keyboard Keys
The kbd element should look like a key, which readers recognize instantly:
kbd {
font-family: var(--font-mono);
font-size: 0.8em;
padding: 0.1em 0.45em;
border: 1px solid var(--color-border);
border-bottom-width: 2px;
border-radius: 0.25rem;
background-color: var(--color-body);
white-space: nowrap;
}
Nest kbd elements for combinations, as the HTML spec suggests: an outer kbd wrapping inner kbd elements for each key, with a plus sign between them. Then reset the outer one so only the individual keys look like keycaps.
File Paths and Commands
File paths are code, so wrap them in code. The difference is that paths are long and often unbreakable, so overflow-wrap: anywhere from the inline code styles above is essential.
For shell commands, a prompt character helps readers recognize a terminal, but it should never be copied. Add it with CSS instead of putting it in the text:
code.language-bash .line::before {
content: "$ ";
color: var(--code-comment);
user-select: none;
}
Program Output
Use samp inside pre for output, and style it with a slightly muted color so it reads as a result rather than something to type.
Add a Copy Button Without Breaking Layout
A copy button saves readers from fiddly selection, especially on touch screens. Position it so it never covers code:
.code-block {
position: relative;
}
.code-block pre {
padding-top: 2.5rem;
}
.code-block .copy-button {
position: absolute;
top: 0.5rem;
right: 0.5rem;
font: 0.75rem/1 var(--font-mono);
padding: 0.375rem 0.625rem;
}
Reserving top padding means the button sits above the first line instead of overlapping long lines. Give the button a real accessible name such as "Copy code", and announce success with an aria-live region so screen reader users know it worked.
Support Dark Mode
Code blocks often stay dark in both themes, which is a valid design choice, but your inline code and token colors still need to meet 4.5:1 on whichever background they sit on. If you switch, define the dark tokens under your dark mode selector:
@media (prefers-color-scheme: dark) {
:root {
--color-pre-bg: #161b22;
--color-pre-text: #e6edf3;
--code-keyword: #ff7b72;
--code-string: #a5d6ff;
--code-function: #d2a8ff;
--code-number: #79c0ff;
--code-comment: #8b949e; /* 5.5:1 on #161b22 */
}
}
Light text on dark backgrounds looks heavier, so if your mono font is variable, dropping the weight slightly in dark mode, say from 400 to 380, keeps it from glowing. The broader considerations are covered in choosing fonts for dark mode.
Code Snippet Styling FAQ
Around 0.875rem, or 14px at default settings, works well for most sites. It is slightly smaller than body text to fit more characters per line, but large enough to read comfortably. Use rem rather than px so it scales with user font settings, and use em for inline code so it scales with its surrounding text.
Scroll by default, because wrapping can make indentation misleading, especially in Python, YAML, and other whitespace-sensitive languages. Soft wrapping is acceptable for shell commands and log output, where readers care more about seeing the whole line than about exact alignment.
Browsers apply a legacy rule that shrinks the default size of text whose font family is only the generic monospace keyword, typically from 16px to 13px. Setting font-size to 1em on code, pre, kbd, and samp resets it, and then you can apply your own deliberate size.
Yes. Code is text, so every token color, including comments, needs at least 4.5:1 contrast against the code block background under WCAG 1.4.3. Comments are the most common failure because many themes deliberately fade them.
Programming ligatures that merge characters like != or => into single glyphs are a personal preference in an editor, but on a tutorial site they can confuse readers who are copying code or learning syntax. Turn them off in published code blocks with font-variant-ligatures set to none.
On the server or at build time whenever you can. Tools like Shiki generate static HTML with colors already applied, so readers download no highlighting JavaScript and see colored code immediately without a flash of plain text.
Conclusion
Good technical typography is mostly about getting the fundamentals right: semantic elements, a monospace stack with clear confusable characters, sizes that are relative to their context, and overflow handling that never breaks the page. Inline code needs subtle contrast and the ability to wrap; code blocks need horizontal scrolling, keyboard access, and room for a copy button.
Syntax highlighting is where most sites quietly fail accessibility, so treat token colors like any other text color and check every one against 4.5:1. Do the highlighting at build time, add line numbers and prompts with CSS so they never end up in a reader's clipboard, and your code examples will be easier to read, easier to copy, and more trustworthy.
Here are some useful references for going deeper on styling code and technical typography:
- MDN Web Docs: The code element — semantics of inline code and its related elements.
- MDN Web Docs: font-size-adjust — matching x-heights across fonts.
- W3C: Understanding Success Criterion 1.4.3: Contrast (Minimum) — contrast thresholds that apply to syntax colors.
- Shiki: Shiki documentation — build-time syntax highlighting with dual light and dark themes.
- MDN Web Docs: The kbd element — how to mark up keys and nested key combinations.


