Insights
DesignDevelopmentInteractive

Why a centred label looks too high

A label centred in a button can still sit a pixel off. I measured the buttons on this site to find out where that pixel goes, and what a small nudge does and does not fix.

Agustin Dell’Aquila12 October 2026, 7 min read

On this page

Every button on this site is a pill with a label in the middle. The label is centred with flexbox, the way everybody does it, and for a long time I did not look at it twice.

Then I looked at it twice. The label was sitting high. Not by much: a pixel, maybe less. Enough that once I had seen it I could not stop seeing it.

This is what I found when I went looking for that pixel.

A pixel too high

The main button here is 46 pixels tall, with 16 pixel type set in Geist. I took a screenshot in Chrome on an ordinary screen, one screen pixel to each CSS pixel, and counted rows. There were 16 rows of pixels above the capital letter and 18 under the baseline.

Figure 1 is that button. Not a drawing of it: the same element with the same class, enlarged, so each square is one pixel of the real thing.

Above the capitals
0px
Under the baseline
0px
The label sits
0
Actual size
Figure 1. The site’s own button, enlarged. The hatched box is the label’s line, which is the thing the layout centres. The two distances are measured on this page and set on your screen’s pixels the way the browser paints them, so they may not be the ones I counted.

On a screen like the one I counted on, the figure says 16.6 where I said 16: it knows a capital is 11.36 pixels tall, and I was counting whole rows. Either way there is more room under the label than over it, and the eye reads that as a label that has floated up.

I measure to the capitals and the baseline because that is where the weight of a label is. The tail of the y hangs lower, but nobody reads a tail as part of the block. The band from the feet of the letters to the tops of the capitals is what looks centred or does not.

The quick fix

My first fix took a minute. If the label sits high, push it down. Every pill on the site got a little padding at the top:

.v3 { --cap: 0.1em; }

.v3 .btn,
.v3 .chip { padding-top: var(--cap); }

Padding at the top makes the space the label is centred in shorter from above, so the label moves down by half of what is added. A tenth of an em on 16 pixel type is 1.6 pixels of padding and 0.8 of a pixel of travel, which lands on the next whole pixel.

On that screen, 16 and 18 became 17 and 17. The nudge under Figure 1 is that same variable: set it to 0.10em and see what your screen makes of it.

It worked, I shipped it, and I did not understand it. A fix I do not understand is a fix I cannot trust at the next size, so I went back.

What the browser centres

Flexbox does not know where the letters are. It centres a box: the label’s line. With line-height: 1 that line is exactly as tall as the type, 16 pixels for 16 pixel type.

Inside the line, the font decides where the letters go. Every font file carries a few numbers for this: how far above the baseline its shapes may reach (the ascent), how far below (the descent), and how tall a capital is. Geist is drawn on a grid of 1000 units. Its ascent is 1005, its descent is 295, and its capitals are 710 tall.

Ascent and descent together are 1300 units, taller than the 1000 unit line, so 150 stick out above the line and 150 below. That puts the baseline 855 units down from the top. The capitals reach up 710 from there, which leaves 145 above them. Under the baseline there are 145 left.

145 over, 145 under. It comes out even because 1005 less 295 is exactly 710. On paper, Geist’s capitals are in the dead centre of their line, and there is nothing to fix.

Where the pixel goes

A screen is not paper. The browser cannot put a baseline 13.68 pixels down. Before it lays out a line it rounds the font’s numbers to whole pixels, each one on its own.

At 16 pixels, an ascent of 16.08 becomes 16 and a descent of 4.72 becomes 5. Together they are now 21, five more than the line, and five does not split evenly: three stick out above and two below. The baseline ends up 13 pixels from the top of the line, where the font asked for 13.68.

The line at 16 pixels, in pixels: what the font asks for and what this browser uses.
The fontThis browser
Ascent16.0816.08
Descent4.724.72
Baseline, from the top of the line13.6813.68
Room above the capitals2.322.32
Room under the baseline2.322.32
Figure 2. One line of type, enlarged. Hatched: the line. Dashed: the font’s ascent and descent. In the browser’s view each square is one pixel at the real size, and its numbers are read from a real line of text on this page, in your browser.

Two thirds of a pixel, upward. Inside the line the capitals now have 1.64 pixels over them and 3 under, and that is the lopsided button from the start of this piece.

The amount changes from size to size. At some the two roundings cancel, at others they stack. What it does not do is grow with the type: it is a rounding error, so it is counted in pixels, not in ems.

One nudge, every size

That is the catch in my quick fix. The error is in pixels and stays small. The nudge is in ems and scales. At 16 pixels a tenth of an em moves the label 0.8 of a pixel. At 24 it moves it 1.2, which is more than there ever was to correct.

Type size, the button
16px
By its line box
0
Nudged 0.10em
0
Figure 3. How far a label sits from the true middle, for type from 10 to 24 pixels. Every dot is a real pill on this page, measured in your browser. The grey band is half a screen pixel either side of the middle: as close as a baseline set on whole pixels can get.

The hollow dots are plain centring. They wander, sometimes over the middle and sometimes under it, and they stay within about a pixel of it at every size. The nudged dots drift: the larger the type, the lower the label.

Then I made my count again, in the same browser, at two screen pixels to each CSS pixel, which is what a dense laptop display or a phone has. There Chrome rounds to half pixels, so the error starts out smaller. The plain button came out at 17 over and 17.5 under. With my nudge it was 18 and 16.5.

The fix that centred the label on one screen pushed it low on the other. And on the first screen it only ever helped the button: the chip and the small button were painted on the same pixels with it as without.

All of that counting was in Chrome. I have not counted in Safari or Firefox, and I would not assume they round the same way. The figures here measure whichever browser you are reading in.

Centre the letters, not the box

The trouble was never the amount. The layout centres a box, and by the time the text is painted the letters are not in the middle of that box. So change the box.

CSS can do that now. text-box-trim cuts a line down to its letters, and if you ask for it, to exactly the band I have been measuring: from the tops of the capitals to the baseline.

.btn > span { text-box: trim-both cap alphabetic; }
Above the capitals
0px
Under the baseline
0px
The label sits
0
Actual size
Figure 4. The same three pills, with the label’s box trimmed to its capitals. The hatched box is now only as tall as the letters, so centring the box centres them, to the nearest pixel.

Now the label’s box is its capitals. Flexbox centres that, and the only rounding left is the last one, when the text is set on a pixel. That can be out by half a screen pixel and no more, at any size. Those are the red dots in Figure 3, and they do not leave the band.

It is recent, and not every browser has it. Where it is missing, the two figures say so.

One thing no property fixes. My chip is 26 pixels tall and its capitals paint 9 rows tall at one pixel to a pixel. That leaves 17 rows to share, and 17 is odd: 8 over and 9 under is the best there is. For that chip to sit dead centre on every screen, it has to be 25 or 27 pixels tall.

What I keep from this

  • Centred, in CSS, means the box is centred. Where the letters are is a separate question.
  • Count it. A screenshot and a zoom will tell you more than a minute of squinting, and it takes less time.
  • Count it on both kinds of screen, and in more than one browser. I tuned a fix on one screen and made the other worse.
  • A rounding error is in pixels. A correction in ems fits the size it was tuned at and misses the rest.
  • If the pill’s height less the height of a capital is an odd number of pixels, nothing will centre it.
  • Where the browser can trim the line to the letters, let it, and keep a nudge for where it cannot.

The nudge is still in my stylesheet as I write this. At least now I know what it is: a patch for one size on one kind of screen.

It was one pixel. I would rather know where it went.