You give a button the same padding on top and bottom. The label still doesn't look centred. So you add a pixel to the top, and it looks right, until someone changes the font.
The padding was never the problem. It's measured from a box you can't see, and that box doesn't fit the letters.
Try it below. Switch fonts, then turn on the trim.
What's in the box
When you set font-size: 16px, you set the size of the em, an imaginary square the letters are designed in. A font's glyphs are drawn on a grid inside it, often 1,000 or 2,048 units tall. But nothing says the letters have to fill it, or stay inside it.
The font also declares two numbers for layout. The ascent is how far above the baseline it needs room for, and the descent is how far below. Together they make the content area, the box the browser actually lays out.
Those numbers have to cover every glyph the font has: an É with its accent, a Å with its ring, the tail of a g or a y. So the box is taller than the letters in most words. Measured in Chrome on macOS, Arial's ascent is 0.91em and its descent is 0.21em. That's a box 1.12em tall around letters whose capitals are 0.72em.
Two more lines matter. The cap height is where flat capitals like H stop, and the x-height is where lowercase letters like x stop. A label like "Save" lives almost entirely between the baseline and the cap height. It never uses the room for accents above or descenders below.
One font, three sets of numbers
It gets messier. For historical reasons, a font file stores its ascent and descent three times.
There's the hhea table, from Apple's original TrueType format. There's a "typo" set in the OS/2 table, meant for typographic line spacing. And there's a "win" set in the same table, which Windows used to decide where to clip glyphs. Which one gets used depends on the operating system and on a flag in the font. Google Fonts' vertical metrics guide sums it up: macOS uses hhea, Windows uses typo only when the font sets the USE_TYPO_METRICS flag, and otherwise uses win.
So if a font's three sets disagree, the same button can sit differently on a Mac and on a Windows PC. Well-made fonts make them match, and the OpenType spec now asks variable fonts to set the flag and keep hhea equal to the typo values. You can't fix this in CSS, but it's worth knowing when a label looks fine on your machine and off on someone else's.
Where line-height puts the rest
Then line-height adds its own space. CSS works out the difference between the line height and the content area, and splits it equally above and below. Each half is called half-leading.
const contentArea = (ascent + descent) * fontSize;
const halfLeading = (lineHeight - contentArea) / 2; // above and below
Equal above and below sounds fair. But it's equal around the content area, not around the letters. If the font's ascent leaves more room above the capitals than its descent leaves below the baseline, the capitals sit low by half the difference, at any line-height and any padding.
// How far the capitals sit below centre, in em
const offset = (ascent - capHeight - descent) / 2;
In Arial that's almost exactly zero, so it centres well. Using the numbers measured above, the system font on a Mac comes out around 0.03em low and Verdana around 0.04em. At 16px that's about half a pixel. On a 44px label it's more than a pixel, and fonts that reserve extra room above for stacked accents can be off by much more.
There's a second effect. Your eye centres the word, not the font. Lowercase letters only reach the x-height, so a word like "save" has most of its ink lower down than "SAVE" does. That's partly a judgement call, so the usual convention is to centre the band from the baseline to the cap height.
Trimming the box
CSS can now shrink the box to the letters. text-box-trim says which edges to trim, and text-box-edge says where to trim them to. The text-box shorthand sets both.
.button {
padding-block: 12px;
text-box: trim-both cap alphabetic;
}
cap trims the top of the first line to the cap height, and alphabetic trims the bottom of the last line to the baseline. Now the label's box is exactly the band "Save" uses, so equal padding looks equal. It's what Figma's vertical trim setting does to a text layer.
As of November 2026, it's supported in Chrome and Edge 133, Safari 18.2 and Firefox 154. MDN lists it as Baseline from August 2026. Older browsers ignore it and you get the untrimmed box, so it's safe to add today. It works on block containers everywhere, but trimming inline elements only works in Firefox, and partly in Safari.
Before this, the fix was Capsize. It reads the font's metrics and generates negative margins that pull the box in to the cap height and baseline. It's what the demo falls back to when your browser can't trim. It works, but the numbers belong to one font. If the font fails to load and the fallback draws instead, the margins are wrong.
The worst fix is the one most of us have shipped: nudging the padding by a pixel until it looks right. It's tuned to one font, at one size, on one platform.
Same font-size, different sizes
The em also explains a related surprise. Set two fonts to the same font-size and one can look much bigger.
font-size sets the em, not the letters. Each font decides how big its letters are inside it. Measured in Chrome on macOS, Verdana's x-height is 0.55em and Times New Roman's is 0.45em. So at the same font-size, Verdana's lowercase letters are about 22% taller.
That matters most when a web font falls back to a system font, or when you mix two families in one line. font-size-adjust fixes it by choosing the x-height instead of the em.
body {
font-family: "Brand Sans", Verdana, sans-serif;
font-size-adjust: from-font; /* keep the first font's x-height */
}
A number like 0.5 means "scale each font so its x-height is half the font-size". from-font takes that number from the first font in the list, so the fallback matches it. It's supported in every major browser, with Chrome the last to add it, in version 127.
All of these come from the same idea. The box CSS gives you was built to hold any text. Your label is one particular word, so if you want it to look centred, measure what the word actually uses.