Set a line of text in a button, centre it vertically, and look at it. It's sitting slightly high, and it'll keep sitting slightly high no matter what you do to the padding, because the thing you centred isn't the thing you're looking at.
Every font reserves a band of empty space above its capitals, and another below its baseline for the descenders, and those two bands are almost never the same size, and nothing in CSS will tell you by how much. The browser measures the font's box, subtracts it from the line height, and splits what's left equally top and bottom. So it centres the box perfectly, and the letters inside that box aren't centred, and they never were.
Anybody who's set type professionally has been nudging things by a pixel for years to correct this, usually without being able to say why. I did it for six years in print software before I ever wrote CSS, where the software just handled it for you, and then spent a good while assuming the web version was my fault.
What print does instead
A baseline grid is one invisible ruler down the page at a fixed pitch, and every line of text in every column sits on it. That's why a well-set spread feels calm even when the columns are different lengths. The type's agreeing with itself across the whole page, rather than each block being individually plausible and the lot of them disagreeing.
It's existed since metal type, for the very practical reason that metal type physically sat on a line. It's the default in InDesign, where it's a checkbox you tick once and never think about again.
On the web it's not a checkbox, and it's not hard for the reason people assume.
It's not hard because of browser inconsistency or vendor prefixes or any of the usual suspects. It's hard because line-height centres text in its box, so the baseline lands wherever that font's particular asymmetry puts it. That position is different for every typeface and different again at every size, and there's no property that will tell you where it landed. There's no number you can write down once.
Watch it happen
Every figure below is measured live. The ascent and descent come off the font your browser actually resolved, not the one the stylesheet asked for, and the drift is computed against the grid you can see behind the text. Change the typeface without changing anything else and watch the number move.
Text does not sit where you think it does
The lines behind the text are a baseline grid at 8px. Watch where the letters actually land on it, then change the typeface without changing anything else.
Typeface
Hamburgefonstiv
Measuring the font as it resolved.
Nothing above is stored. The ascent and descent are read off the font the browser actually resolved, using canvas metrics, and the drift is computed from them against the grid you can see. Switch between Satoshi and Georgia at the same size and the offset moves, which is the whole argument: there is no single number that fixes this, so it has to be measured.
Satoshi and Georgia at the same size land in different places, and that's the entire problem in one control. Any fix that's a hardcoded offset is a fix for exactly one typeface at exactly one size, and it silently stops being true the moment either changes.
So measure the font
The metrics are sitting right there and almost nobody uses them. Canvas TextMetrics gives you the font's own ascent and descent, already scaled to the rendered size, so there's no font file to parse and no build step, and you get the metrics of the font that actually resolved rather than the one you hoped for. That last part is the one that matters. A stack that fell back to a system font has different numbers, and a tool that trusts the stylesheet will tell you confidently that you're on a grid you're nowhere near, which is worse than never having measured at all.
const ctx = canvas.getContext("2d")
ctx.font = "700 18px Satoshi, sans-serif"
const box = ctx.measureText("Hxp")
// box.fontBoundingBoxAscent -> what the browser builds the line box from
// box.fontBoundingBoxDescent
const halfLeading = (lineHeight - (ascent + descent)) / 2
const baseline = halfLeading + ascent // where the letters actually sitH and x rather than O and o, and that isn't fussiness. Round letters overshoot the line they sit on by a percent or two, on purpose, because otherwise they read smaller than the flat ones sitting beside them. Measure a curve and you get a cap height that's slightly too tall. Then a grid that's quietly wrong everywhere, from one letter.
The half nobody builds
Computing the offset is arithmetic, and Capsize has done it well for years. text-box-trim does it in CSS now, in Chrome and Safari, not yet in Firefox. None of them answer the question that actually matters. Is the page you shipped on the grid you designed?
You can't answer that from the source. Inherited line-heights, and a component library's own reset, and a webfont that didn't load, and one heading with a clamp() that resolves to something the scale never anticipated. All of it lives in the gap between what the stylesheet says and what the browser drew. The only place to answer it is the rendered page.
So the tool walks the document, measures where every first line actually landed, and reports the ones that are off. Same reasoning as the contrast gate I wrote after claiming a standard I couldn't enforce. The source describes an intention, and an intention isn't a result.
One shared offset is a wrong origin, and that's a one-line fix. Scattered drift means your type scale and your spacing scale disagree, which is a design problem wearing a CSS costume.
It reports which of those two you have, because they want completely different responses and they look identical in a screenshot.
What it will not do
It won't snap your headlines. Body text is the job, and a headline at 4rem with tight leading is a shape rather than a line of reading, and forcing one onto an 8px rhythm just makes it worse, so display type opts out by selector, which is deliberate rather than an oversight. A grid you can't opt out of gets switched off entirely, and then you've got no grid at all.
It won't fail on half a pixel either. Sub-pixel layout and fractional line-heights land baselines a fraction off a whole number constantly, and a tool that goes red on 0.02px is a tool you uninstall by Tuesday.
Why bother
Honestly, because it has annoyed me for years and I finally had the tools to go and find out whether it was real or whether I was imagining it. It's real, it's measurable, and it comes out at roughly one to three pixels at reading size, which is exactly the amount that makes something feel slightly off without anyone being able to tell you what is wrong with it.
And there's a bigger version of that. Most of what separates typography that feels considered from typography that doesn't is a run of small corrections nobody consciously notices, made by people who were trained to notice them, and the whole craft lives in that gap. Very few of those have made it into the tooling, and this is one. I'd like to know how many others are sitting there in the same condition.
Since writing this
This piece describes a tool that finds the problem, because that's all it did when I wrote it. It corrects now, and that turned out to be a much harder job than finding, and I got it wrong three times before it worked. If you came here for the tool rather than the problem, the seating story is the one to read: three-times-i-thought-the-page-was-on-the-grid.