Skip to content
Typography

Optically centring button text with text-box

Why text sits too low even with equal padding above and below, what text-box-trim and text-box-edge change about it, which edges suit which typeface and where the property does not apply.

8 min readBy Amelie RoesmannUpdated: 07 November 2026
TypographyCSSButtonsLayout

Almost every button has the same small problem: the padding is equal above and below, and the label still sits visibly too low. The reason is not the button but the typeface. Every line brings space above the capitals and below the baseline that the font reserves for accents and descenders, and that space is usually larger at the top than at the bottom. Until now the only help was trial and error: one pixel more padding at the bottom, a negative margin, a correction that is wrong again the next time the font changes. text-box cuts that space off, so the box ends exactly at the top of the capitals and at the baseline. Safari was the first browser to ship the property, in version 18.2 in December 2024, Chrome followed with version 133 in early 2025, Firefox with version 154. Since August 2026 MDN lists it as Baseline. This guide shows where the offset comes from, which values exist, where the property helps and where it does not apply.

The short version
  • Text sits too low in a button because the line brings more empty space above the capitals than below the baseline. Equal padding therefore produces unequal distances to the visible type.
  • text-box: trim-both cap alphabetic cuts the first line at cap height at the top and the last line at the baseline at the bottom. After that, the padding equals the distance you see.
  • The property applies to block containers and inline boxes and is not inherited. On a button with inline-flex it therefore belongs on the element holding the text, not on the button.
  • Browsers without support show the button as before. The declaration is an addition you can add without a query, as long as the padding still looks acceptable untrimmed.
01

Why does text in a button sit below the middle?

Because a line's box does not end at the type but at the measurements the font file specifies. Above the capitals there is room for accents such as Ä or É and for ascenders, below the baseline room for the descenders of g, p and y. On top of that comes the share of line-height, added in equal parts above and below. In a button with a word in capitals or with a capital initial, the space at the top is almost always empty, the one at the bottom too, but it is smaller.

You can see the result on every button with equal padding: the distance from the top edge to the letters is larger than the distance from the baseline to the bottom edge. How large the difference is depends entirely on the typeface. That is why no fixed rule such as one pixel more at the bottom helps: it holds for one font at one size and is wrong at the next change.

The usual corrections all start in the wrong place. Unequal padding moves the text, but only for that font size. A negative margin on the text element moves the line, but the layout around it keeps calculating with the old height. And line-height set to 1 shrinks the line evenly according to the font's metrics, not according to what you see.

The padding is right. The line is larger than the type.

02

What exactly does text-box-trim do?

It cuts away the space between the edge of the box and the visible type, at the top of the first line, at the bottom of the last one, or both. Where exactly it cuts is set by text-box-edge. With cap the box ends at the top at cap height, with alphabetic at the bottom at the baseline. After that, the padding is the distance you also see.

The text-box shorthand combines both. text-box: trim-both cap alphabetic cuts at cap height at the top and at the baseline at the bottom, and because missing parts are filled in, text-box: cap alphabetic is enough. The initial value is normal, meaning no cut. The property is not inherited and applies to block containers and inline boxes.

Safari shipped it first, in version 18.2, Chrome in version 133. Firefox has supported it since version 154 according to caniuse, and MDN has listed it as Baseline since August 2026. Early drafts called it leading-trim, and older posts still show examples under that name that no longer work today.

.button {  padding: 0.75rem 1.25rem;  line-height: 1.4;} /* the box now ends at cap height and at the baseline */  .button {    text-box: trim-both cap alphabetic;  }
Same padding, two results. Only with the cut does the space at the top match the space at the bottom.
Choose the padding again afterwardsAdding text-box to an existing button takes a few pixels of height off the top and bottom, so the button gets flatter. That is correct, because part of the distance used to be invisible space from the font, but it means the vertical padding can usually be a little larger afterwards. The best approach is to set it so the button has the intended height with the cut, then check once without it that it does not look cramped there.
03

Which edges suit which typeface?

For buttons, labels and headings cap alphabetic is almost always the right choice. Most labels start with a capital or consist entirely of them, and the eye measures the space at the top at exactly that edge. At the bottom it measures at the baseline, not at the descenders, which may reach into the padding with a g or a y.

Text set entirely in lowercase is different. There are often no capitals for the eye to orient itself by, and the visible top edge sits at x-height. For that case there is ex as the top edge. A button labelled next or a tag in lowercase then sits in the middle where cap would show it too low.

The third edge, text, matches the font's starting state and is useful where you only want to cut one side. With trim-start or trim-end you can, for example, cut only the top of a heading so it starts flush with an image beside it, while the space to the following paragraph stays the same.

  1. cap alphabetic For buttons, labels, figures in stats and headings with a capital initial. The most common choice.
  2. ex alphabetic For labels entirely in lowercase, where x-height is the visible top edge.
  3. trim-start cap Cut only at the top, for instance so a heading lines up with the top edge of an image or a rule.
04

Why does text-box not work on my flex button?

Because the property applies to block containers and inline boxes, and a flex container is neither. Many buttons use inline-flex so that icon and text sit side by side, centred. The text inside then ends up in an anonymous flex item, and because text-box is not inherited, the declaration on the button never reaches that text.

The fix is a dedicated element around the label, usually a span, and the declaration belongs on that element. An inline box is explicitly included, and as a flex item the span becomes a block anyway. The button keeps its inline-flex, the icon stays centred through align-items, and only the text loses the empty space above and below it.

On a button set to inline-block this is not needed, because an inline-block is a block container. The easiest way to check is in the browser: in the developer tools the box view shows the height of the text element, and with the cut it has to be visibly smaller than without.

<button class="inline-flex items-center gap-2 px-5 py-3">  <svg aria-hidden="true">...</svg>  <span class="[text-box:trim-both_cap_alphabetic]">Start the trial</span></button>
The button stays a flex container. The label inside it is what gets cut.
On the button
  • The button uses inline-flex, and the text-box declaration sits right on it.
  • The text ends up in an anonymous flex item and does not inherit the declaration.
  • The label keeps the empty space above and below it.
  • Measuring shows the same height as before, and the property looks broken.
On the label
  • The button stays on inline-flex, icon and text still sit centred side by side.
  • The label sits in a span of its own, and the text-box declaration goes there.
  • The span ends at cap height at the top and at the baseline at the bottom.
  • The button's padding is now the distance you see.
Do not set it on html or bodyBecause text-box is not inherited, a declaration at the very top of the document does nothing for the elements below. A selector that matches everything, on the other hand, also cuts running text, lists and table cells and shifts spacing that used to be right. A dedicated class for the places where the cut is wanted works better: buttons, labels, headings next to images.
05

Where does text-box help beyond buttons?

Anywhere text should line up with a visible edge. The most common example is a card with image and heading side by side: without the cut the heading starts a few pixels below the top of the image, even though both sit on the same line in the grid. With trim-start cap, cap height lines up flush with the image.

The second example is spacing between heading and text. A gap of 16 pixels in code is often 22 or 24 on screen, because there is invisible font space below the heading and above the paragraph. Cutting both sides gives you the gap the layout specifies, and a spacing system made of a few values actually becomes visible on the page.

The third is small elements with a background: tags, counters in a circle, figures in a tile. The offset is most noticeable at small sizes, because a few pixels make up a large share of the height. A counter with a single digit in a circle sits in the middle with cap alphabetic, without anyone nudging it by hand.

@utility text-trim {  text-box: trim-both cap alphabetic;} @utility text-trim-top {  text-box: trim-start cap alphabetic;} <h3 class="text-trim-top">Field notes</h3><span class="text-trim rounded-full px-3 py-1.5">New</span>
One class for every place where the cut is wanted. In Tailwind v4 as a utility of its own.
06

What happens in browsers without support?

Nothing that looks broken. A browser that does not know text-box skips the declaration and shows the line as it looked before, with the slight downward offset. So the property adds something on top, it replaces nothing, and an @supports query is not needed for the simple case.

A query is only worth it when the padding is tuned to the cut and would be too large without it. Then you write the padding for the untrimmed version as the default and put the smaller or larger padding together with text-box in an @supports (text-box: cap alphabetic) block. That way every browser gets the height that suits its behaviour.

Two looks are enough to check. In a current browser the space above the capitals has to equal the space below the baseline, easiest to see with a word without descenders like START. In an older browser the button may sit a little lower, but not so tight that the type touches the edge.

.button {  padding-block: 0.625rem;}   @supports (text-box: cap alphabetic) {  .button {    padding-block: 0.875rem;    text-box: trim-both cap alphabetic;  }}
Only needed when the padding is tuned to the cut. Otherwise the declaration on its own is enough.

First the version that carries everywhere. Then the cut on top.

Share
Written by
Amelie RoesmannCreative direction

We build websites and web apps at Systra Studios in Münster and put the building blocks from that work here. What you read comes from real projects, not from a brochure.

Systra Studios: web design from Münster

One button, one line of CSS.
The sections around it are ready to take.

Call-to-action sections with a heading, a short text and a button at the centre. Copy them, put the text-box class on the label and set the padding once more.

  • One file per component, as source code in your own project.
  • Built with React, TypeScript and Tailwind CSS v4.
  • Every component respects reduced motion.
Browse CTA sections
FAQ

Frequently asked questions

text-box is the shorthand for the other two. text-box-trim decides whether and on which side to cut: none cuts nothing and is the initial value, trim-start cuts the top of the first line, trim-end the bottom of the last one, trim-both both. text-box-edge decides where to cut: at the top at text, cap or ex, at the bottom at text or alphabetic. Without text-box-trim, text-box-edge has no effect at all. In the shorthand the browser fills in missing parts itself: if the trim part is missing, trim-both applies, if the edge part is missing, auto applies, which equals text. So text-box: cap alphabetic means the same as text-box: trim-both cap alphabetic, and text-box: normal resets both. Early drafts called the property leading-trim, and older articles still use that name.

Every major engine in its current version. Safari was first with version 18.2 in December 2024, on the iPhone too. Chrome followed with version 133 in early 2025, and Edge and Opera come along through Chromium. Firefox has supported the property since version 154 according to caniuse, and since August 2026 MDN lists it as Baseline, meaning newly available in all major browsers. It is not one of the features Interop 2026 sets out to align. For older browsers this is not critical: they ignore the declaration and show the line untrimmed, with the offset you already know. The only thing that matters is choosing padding that does not make the button look cramped without the cut.

The quickest way is an arbitrary property in square brackets, so [text-box:trim-both_cap_alphabetic] right on the element. The underscore stands for the space that cannot appear in a class. If you need the declaration in many places, create your own class in Tailwind v4 with @utility, say text-trim, and write text-box: trim-both cap alphabetic inside it. The markup then carries a readable name instead of a long bracket, and changing the edges happens in one place. For buttons with inline-flex the same applies as without Tailwind: the class belongs on the span holding the label, not on the button itself, because a flex container is not one of the boxes the property applies to.

No, the two control different things. line-height sets the distance between lines and stays exactly as important for multi-line text as before. text-box only cuts on the outside, at the top of the first line and the bottom of the last one, and the space between lines is untouched. What text-box replaces is the trick of setting line-height to 1 or below so a single-line button does not get too tall. That trick does shrink the line, but it cuts in the wrong place, evenly above and below according to the font's metrics, not at cap height and the baseline. With a two-line label it also makes the lines touch. With text-box, line-height stays at a readable value and only the edge of the box moves to the visible type.

Didn't find your question? We're happy to help.

Get in touch