Making text actually vertically center with CSS
Text is surrounded by invisible space due to font metrics. This “leading space” causes alignment challenges: text doesn’t visually center with the line-height, buttons appear slightly off-center, and spacing feels inconsistent. Two CSS properties address this problem at different levels: text-box-trim for inline text and margin-block for block-level layouts.
The Problem: Leading Space in Fonts
Every font has metrics that define space above and below the actual visible letters:
- Cap height: The height of uppercase letters (like “A”, “B”, “M”)
- Baseline: The invisible line that letters sit on
- x-height: The height of lowercase letters like “x” (from baseline to top)
- Ascent: Space above the baseline (includes cap height plus room for accents)
- Descent: Space below the baseline (for descenders like “g”, “y”, “p”)
- Line-height box: The total vertical space allocated for a line of text
What is leading space?
Imagine text like “Hello” in a box. Visually, you see the letters taking up a certain height. But the browser allocates extra invisible space:
- Above the text: From the top of the line-height box to the top of the cap height
- Below the text: From the baseline to the bottom of the line-height box
This invisible padding is “leading space” (pronounced “led-ing”, from traditional typesetting where lead strips created space between lines).
Visual problem: Text doesn’t appear centered even though it technically is. Buttons look misaligned. Spacing feels off because the leading space throws off visual balance.
Solution 1: text-box-trim (Inline Level)
The experimental text-box-trim property removes leading space for inline text, making it possible to align text precisely.
Practical Use
.text {
font-size: 32px;
text-box-trim: trim-both;
text-box-edge: cap alphabetic;
}
Your browser doesn’t support text-box-trim yet. The example above won’t
show the trimming effect. Consider using the margin-block technique below
as a fallback.
How It Works
text-box-trim trims the leading space above and/or below text by specifying which edges to trim:
| Behavior | Use case | |
|---|---|---|
| none | No trimming (default) | Keep font metrics as-is |
| trim-start | Remove space above | Reduce space above first lines |
| trim-end | Remove space below | Reduce space below last lines |
| trim-both | Remove space above and below | Achieve tight alignment |
Combined with text-box-edge to specify which font metrics to use as boundaries:
.text { text-box-trim: trim-both; text-box-edge: cap alphabetic; /* Trim from cap height to alphabetic baseline */ }
Understanding text-box-edge values:
- cap: Uses the cap height (top of capital letters) as the edge
- alphabetic: Uses the alphabetic baseline (the line letters sit on) as the edge
- ex: Uses the x-height (top of lowercase “x”) as the edge
- text: Uses the em-box (default font metrics box) as the edge
The combination cap alphabetic means “trim everything above cap height and below the baseline,” which gives you the tightest possible fit around the visual letters.
Solution 2: margin-block (Block Level)
The margin-block property, combined with font-relative units (cap and lh), achieves the same effect as text-box-trim but at the block level and with much better browser support.
Practical Use
.text {
font-size: 32px;
line-height: 1.75;
margin-block: calc(0.5cap - 0.5lh);
}
How It Works
The formula calc(Xcap - Ylh) centers content by offsetting the cap height against the line-height:
.centered-button { margin-block: calc(0.5cap - 0.5lh); }
Let’s say you have text with:
font-size: 16pxline-height: 1.5(so1lh = 24px)cap height: 0.7em(so1cap ≈ 11.2px)
Without any adjustment:
- The line-height box is 24px tall
- The visual cap height is only ~11.2px
- The browser centers the 11.2px caps within the 24px box
- This leaves ~6.4px above and ~6.4px below the caps
But you want to center based on the visual caps (11.2px), not the full line-height (24px).
The formula calc(0.5cap - 0.5lh) does this:
0.5cap= half the cap height = ~5.6px0.5lh= half the line-height = 12px0.5cap - 0.5lh= 5.6px - 12px = -6.4px
This negative margin pulls the text up by 6.4px, removing the extra space above the caps and visually centering the content. You’re centering the visual caps, not the line-height box.
Font-Relative Units
| Represents | Use case | |
|---|---|---|
| cap | Height of capital letters | Reference for text metrics |
| lh | The computed line-height | Reference for line box height |
| ex | Height of lowercase “x” | For x-height-based sizing |
Comparing the Approaches
| text-box-trim | margin-block | |
|---|---|---|
| Level | Inline | Block |
| Scope | Single/multiple lines of inline text | Block containers |
| Browser support | Limited (experimental) | Good (uses standard units) |
| How it works | Trims leading space directly | Offsets margin to achieve same effect |
| Best for | Precise inline text alignment | Button/badge centering, multi-line blocks |
| Syntax | CSS property | calc() with font-relative units |
When to Use Each
Use text-box-trim (with margin-block fallback):
- You want the most semantically correct approach
- You’re working with inline text or single logical units
- You can use
@supportsfor feature detection - You want cutting-edge browsers to get the best experience
Use margin-block only:
- You need reliable browser support today (good support for
cap/lhunits) - You’re styling buttons, badges, or heading containers
- You’re comfortable with calc() math
- You’re working at the block level
Progressive enhancement pattern (recommended):
- Start with
margin-blockas the fallback - Use
@supports (text-box-trim: trim-both)to apply text-box-trim when available - Always cancel
margin-blockinside the@supportsblock to prevent double adjustment
Common Mistakes
Don’t apply both techniques directly—they will stack and create double the adjustment! Browsers that support text-box-trim also support cap/lh units. Always use @supports (text-box-trim: trim-both) to detect support and cancel margin-block when text-box-trim is available.
The formula only works when you have a defined line-height. If line-height: normal, the lh unit will vary by font and may not give expected results. Always set an explicit line-height when using this technique.
Different fonts have different cap heights relative to their em-box. A formula that works perfectly for one font may need adjustment for another. Test with your actual font choice.
While margin-block can work with multi-line text, the effect is most predictable for single lines. For multi-line content, the leading space between lines also affects visual balance.
Progressive Enhancement
Use @supports to prevent stacking both approaches (browsers that support text-box-trim also support cap/lh units):
.button-text { /* Baseline: Works in all browsers */ line-height: 1.2; /* Fallback: Use margin-block for browsers without text-box-trim */ margin-block: calc(0.5cap - 0.5lh); } /* Only apply text-box-trim if supported, and cancel margin-block */ @supports (text-box-trim: trim-both) { .button-text { margin-block: 0; /* Cancel the margin-block trick */ text-box-trim: trim-both; text-box-edge: cap alphabetic; } }
This ensures:
- Browsers without
cap/lhsupport show normal text (no adjustment) - Browsers with
cap/lhbut nottext-box-trimuse the margin-block trick - Browsers with
text-box-trimuse that instead and cancel margin-block to prevent double adjustment
Real-World Scenarios
Button text centering
Challenge: Button text appears off-center vertically because of leading space, especially noticeable with taller buttons or larger font sizes.
Solution:
.button { display: inline-flex; align-items: center; padding: 0.75em 1.5em; line-height: 1.2; } .button-text { margin-block: calc(0.5cap - 0.5lh); }
Badge or pill components
Challenge: Small badges with tight padding show uneven spacing due to leading space.
Solution:
.badge { display: inline-block; padding: 0.25em 0.5em; line-height: 1; font-size: 0.875rem; margin-block: calc(0.5cap - 0.5lh); border-radius: 9999px; }
Hero heading alignment
Challenge: Large hero headings need to align precisely with other design elements (images, lines, etc.).
Solution:
.hero-heading { font-size: 4rem; line-height: 1.1; /* Fallback for browsers without text-box-trim */ margin-block: calc(0.5cap - 0.5lh); } @supports (text-box-trim: trim-both) { .hero-heading { margin-block: 0; text-box-trim: trim-both; text-box-edge: cap alphabetic; } }
Icon + text alignment
Challenge: When placing icons next to text, the icon aligns to the line-height box, not the visual letters.
Solution:
.icon-text-container { display: flex; align-items: center; gap: 0.5em; } .icon-text-container .text { margin-block: calc(0.5cap - 0.5lh); }
This ensures the text visually aligns with the center of the icon.
Interactions with Other Alignment Properties
With flexbox align-items
.flex-container { display: flex; align-items: center; /* Centers based on flex item height */ } .flex-item-text { margin-block: calc(0.5cap - 0.5lh); /* Further adjusts for visual centering */ }
align-items: center centers the entire flex item (including its line-height box). Adding margin-block then centers the visual text within that already-centered box. This gives you pixel-perfect alignment.
With vertical-align
.inline-container { vertical-align: middle; /* Aligns to the middle of the line-height box */ } .inline-container .text { text-box-trim: trim-both; /* Trims leading space for visual alignment */ text-box-edge: cap alphabetic; }
vertical-align: middle aligns based on the line-height box. text-box-trim then removes the leading space, making the visual alignment match the technical alignment.
With grid align-items
.grid-container { display: grid; align-items: center; /* Centers based on grid cell height */ } .grid-item-text { margin-block: calc(0.5cap - 0.5lh); /* Adjusts for visual centering */ }
Grid alignment works on the box level, margin-block works on the visual level.
Browser Compatibility
- text-box-trim: Limited support (experimental), check caniuse.com
- Font-relative units (cap, lh): Good support, check MDN
- margin-block: Good support (part of CSS Logical Properties)