I’ve spent most of my life sizing things in pixels. It feels satisfyingly exact — a 720px sidebar, an 18px button, a 300px card. Then I rebuilt one of my smaller sites and realized something uncomfortable: I was typing numbers that described nothing real.

Close-up of metal movable type letters on a letterpress case, illustrating typography and the CSS ch character-width unit
Image: Céréales Killer via Wikimedia Commons (CC BY 2.5)

Pixels are a lie, and that’s fine

Here’s the honest part most tutorials skip. A CSS pixel has never been a physical pixel. On a high-DPI phone, one CSS pixel can cover four or more tiny hardware dots. That’s not a bug — it’s the whole trick that makes a 16px label look roughly the same size on a pocket phone, a laptop, and a 50-inch TV. MDN puts it plainly: the lengths are perceptual.

That’s genuinely useful. The problem crops up when I treat px as if it were a physical measure — a fixed ruler I can hold up to the screen. It isn’t. The moment a user changes their browser’s default font size, or a font renders a little wider than I assumed, my carefully tuned pixel numbers start lying in a different direction.

The fix isn’t to swear off px forever. It’s to reach for relative units that scale with the text, specifically the ch unit, when the thing you care about is readability. I’ve been experimenting with exactly this on a text-first site, and it changes how you think about layout.

What the ch unit actually is

ch stands for “character” only by convenience. In the CSS spec it’s defined as the advance measure of the glyph “0” (the numeral zero) in the font currently in use on that element. In plain words: one ch is the width of a “0” as that font draws it.

That sounds trivial, but it unlocks something valuable. Because ch tracks the shape of an actual character, a block sized in ch stays proportional to the actual letters inside it — no matter what font, what size, or what device.

Eric Meyer did the clearest write-up of this I’ve found. The catch he highlights: unless you’re using a monospace font, 1ch is not one character wide. In most proportional fonts the zero is on the wider side, so aiming for an “80-character” column means setting your max-width somewhere around 60ch. It’s directional, not literal — and that’s exactly how you should treat it.

The one-line trick that made my posts readable

My core reading column used to be a fixed pixel width. I replaced it with this:

article {
  max-width: min(60ch, 100%);
}

That single line is doing a lot. The min() function caps the column at 60 character-widths, and when the viewport is narrower than that — a phone, a cramped window — the 100% part takes over and the column fills the screen instead of overflowing. It’s the same pattern Terence Eden uses on his own text-first site, and the official definition lives in the MDN guide to CSS values and units.

Why 60 and not 75 or 80? Readability research on line length suggests most people read comfortably at roughly 50 to 75 characters per line. Because of the proportional-font caveat, 60ch lands in that sweet spot for typical body text without much fiddling. It’s the same reason long-form sites cap their measure in characters rather than pixels. Eric Meyer’s deep dive on the ch unit explains the whole story, including why “character width” is only approximate for proportional fonts.

This is the exact approach Terence Eden described on his text-first blog, and it’s the backbone of building your own little corner of the web without obsessing over breakpoints. If you’re curious about the whole philosophy of owning that corner yourself, my The $0.01/Day Website guide goes through the full setup.

Margin and padding: match the text, not the monitor

Once the column width started scaling with characters, the next thing that felt wrong were my pixel gutters. A 24px gap under a heading is a fixed chunk of air. On a huge display it looks tiny; on a phone it eats the screen.

For horizontal spacing around text, reusing ch keeps the rhythm in proportion to what you’re reading:

.article-footer {
  margin-top: 2ch;
  padding-left: 1ch;
}

For vertical spacing, though, character width is the wrong ruler. A line of text is tall, not wide. That’s where the ex unit earns its keep — it’s the height of a lowercase “x” in the current font, a decent proxy for the height of the body of a letter. Combining them fired most of my arbitrary-looking numbers:

h2 {
  margin-top: 1.5ex;
  margin-bottom: 1ch;
}

There’s also lh, equal to the computed value of line-height. It’s superb for keeping elements exactly one line apart, so vertical rhythm holds even after you tune your line spacing. A commenter on the original post called it their go-to for “making things look right,” and honestly that sums up the whole approach.

But now the trap — web fonts will bite you

Here’s the part that cost me a half-hour of confusion, and it’s the reason I said “directional, not literal” above. A ch is only a stable measure once the final font is actually rendering. Before that, a fallback font is drawing the characters — and its zero could be a totally different width.

That matters the moment you load a web font. The browser paints your page in a fallback font first, measures 60ch against that zero, then swaps in your pretty web font — whose zero is wider or narrower. The column silently changes width, the page jolts, and your Core Web Vitals take a hit.

Cloud Four ran straight into this on their Cloudinary blog rebuild. Their write-up on layout shifts with ch units shows how a reading-width column in ch plus an async-loaded font produced real Cumulative Layout Shift on desktop. Their fix was elegant: swap the container to rem (which ignores the font’s letterforms entirely and stays consistent), and keep the character-based sizing applied where you actually want it — on the text measures like margins and gutters. If you’re running system fonts, the shift mostly disappears, which is one less thing to fret about.

Working through a pixel-to-ch conversion

Let me make this concrete rather than theoretical. Open up a plain HTML file and try it — this is the exact demo I ran locally while writing this.

Start with a root font size and a serif body stack:

:root { font-size: 16px; }
body { font-family: Georgia, serif; line-height: 1.6; }

Then create two columns to compare:

.px-column { max-width: 720px; }
.ch-column { max-width: min(60ch, 100%); }

Give each a few paragraphs of real text and resize the browser. The 720px column is static — it stays 720px wide whether your text is big or small. The 60ch column tracks the zero glyph, so it hugs the text. When I served mine locally and hit it with a browser, the difference was obvious in seconds: the character-sized column just sat better.

If something doesn’t grow when you change the root font-size, check the obvious gotchas. A common one: mindlessly typing padding: 0.75en — there is no en unit, and your padding quietly does nothing. It’s em or ch or ex, pick one. And remember ch is resolved against the element’s own font, so a heading in one typeface and body copy in another won’t share the same character width, no matter how aligned they look.

Should you purge every px? No

Let me clear up a misconception before it spreads. Going full ch-everywhere is optimization theater. Borders, box shadows, icons, small fixed paddings on buttons, and media queries work fine in pixels — and sometimes better in pixels, because those things genuinely map to physical spacing, not to reading rhythm.

Treat this as a few targeted swaps where it actually pays off: the main text column, the gutters around prose, the space between headings and body copy. That’s maybe a dozen lines on most pages, and it buys you typography that adapts to the reader instead of the monitor.

The bottom line

Pixels aren’t evil — they’re just the wrong tool for the job when the job is readable text. The ch unit, plus ex and lh for vertical rhythm, let your layout inherit the intelligence of the font already doing the reading. That’s a nicer way to build than staring at a ruler.

If you’re already deep in the self-hosted, own-your-stack world — or thinking about going there — this kind of fine control is exactly why rolling your own hosting with tools like Knot DNS appeals. And it’s a reminder that the small craft details, like a comfortable line length, are what separate a page people skim from a page people actually finish. As AI-generated pages get noisier, that human readability is only going to matter more — something worth keeping in mind when you think about how people choose what to read at all.

Filed under Tech & Gadgets
Last Update: August 24, 2026 by Felix AlterEgo
0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Newest
Oldest Most Voted