Two CSS Properties For Trimming Text Box Whitespace
The text-box-trim
and text-box-edge
properties in CSS enable developers to trim specifiable amounts of the whitespace that appear above the first formatted line of text and below the last formatted line of text in a text box, making the text box vertically larger than the content within.
This whitespace is called leading, and it appears above and below (so it’s two half-leadings, actually) all lines of text to make the text more readable. However, we only want it to appear in between lines of text, right? We don’t want it to appear along the over or under edges of our text boxes, because then it interferes with our margins, paddings, gaps, and other spacings.
As an example, if we implement a 50px
margin but then the leading adds another 37px
, we’d end up with a grand total of 87px
of space. Then we’d need to adjust the margin to 13px
in order to make the space 50px
in practice.
As a design systems person, I try to maintain as much consistency as possible and use very little markup whenever possible, which enables me to use the adjacent-sibling combinator (+
) to create blanket rules like this:
/* Whenever <element> is followed by <h1> */
<element> + h1 {
margin-bottom: 13px; /* instead of margin-bottom: 50px; */
}
This approach is still a headache since you still have to do the math (albeit less of it). But with the text-box-trim
and text-box-edge
properties, 50px
as defined by CSS will mean 50px
visually:
Disclaimer: text-box-trim
and text-box-edge
are only accessible via a feature flag in Chrome 128+ and Safari 16.4+, as well as Safari Technology Preview without a feature flag. See Caniuse for the latest browser support.
Start with text-box-trim
text-box-trim is the CSS property that basically activates text box trimming. It doesn’t really have a use beyond that, but it does provide us with the option to trim from just the start, just the end, both the start and end, or none
:
text-box-trim: trim-start;
text-box-trim: trim-end;
text-box-trim: trim-both;
text-box-trim: none;
Note: In older web browsers, you might need to use the older start
/end
/both
values in place of the newer trim-start
/trim-end
/trim-both
values, respectively. In even older web browsers, you might need to use top
/bottom
/both
. There’s no reference for this, unfortunately, so you’ll just have to see what works.
Now, where do you want to trim from?
You’re probably wondering what I mean by that. Well, consider that a typographic letter has multiple peaks.
There’s the x-height, which marks the top of the letter “x” and other lowercase characters (not including ascenders or overshoots), the cap height, which marks the top of uppercase characters (again, not including ascenders or overshoots), and the alphabetic baseline, which marks the bottom of most letters (not including descenders or overshoots). Then of course there’s the ascender height and descender height too.
You can trim the whitespace between the x-height, cap height, or ascender height and the “over” edge of the text box (this is where overlines begin), and also the white space between the alphabetic baseline or descender height and the “under” edge (where underlines begin if text-underline-position
is set to under
).
Don’t trim anything
text-box-edge: leading
means to include all of the leading; simply don’t trim anything. This has the same effect as text-box-trim: none
or forgoing text-box-trim
and text-box-edge
entirely. You could also restrict under-edge trimming with text-box-trim: trim-start or over edge trimming with text-box-trim: trim-end
. Yep, there are quite a few ways to not even do this thing at all!
Newer web browsers have deviated from the CSSWG specification working drafts by removing the leading
value and replacing it with auto
, despite the “Do not ship (yet)” warning (*shrug*).
Naturally, text-box-edge
accepts two values (an instruction regarding the over edge, then an instruction regarding the under edge). However, auto
must be used solo.
text-box-edge: auto; /* Works */
text-box-edge: ex auto; /* Doesn't work */
text-box-edge: auto alphabetic; /* Doesn't work */
I could explain all the scenarios in which auto would work, but none of them are useful. I think all we want from auto
is to be able to set the over or under edge to auto and the other edge to something else, but this is the only thing that it doesn’t do. This is a problem, but we’ll dive into that shortly.
Trim above the ascenders and/or below the descenders
The text
value will trim above the ascenders if used as the first value and below the descenders if used as the second value and is also the default value if you fail to declare the second value. (I think you’d want it to be auto
, but it won’t be.)
text-box-edge: ex text; /* Valid */
text-box-edge: ex; /* Computed as `text-box-edge: ex text;` */
text-box-edge: text alphabetic; /* Valid */
text-box-edge: text text; /* Valid */
text-box-edge: text; /* Computed as `text-box-edge: text text;` */
It’s worth noting that ascender and descender height metrics come from the fonts themselves (or not!), so text can be quite finicky. For example, with the Arial font, the ascender height includes diacritics and the descender height includes descenders, whereas with the Fraunces font, the descender height includes diacritics and I don’t know what the ascender height includes. For this reason, there’s talk about renaming text
to from-font
.
Trim above the cap height only
To trim above the cap height:
text-box-edge: cap; /* Computed as text-box-edge: cap text; */
Remember, undeclared values default to text, not auto (as demonstrated above). Therefore, to opt out of trimming the under edge, you’d need to use trim-start instead
of trim-both
:
text-box-trim: trim-start; /* Not text-box-trim: trim-both; */
text-box-edge: cap; /* Not computed as text-box-edge: cap text; */
Trim above the cap height and below the alphabetic baseline
To trim above the cap height and below the alphabetic baseline:
text-box-trim: trim-both;
text-box-edge: cap alphabetic;
By the way, the “Cap height to baseline” option of Figma’s “Vertical trim” setting does exactly this. However, its Dev Mode produces CSS code with outdated property names (leading-trim
and text-edge
) and outdated values (top
and bottom
).
Trim above the x-height only
To trim above the x-height only:
text-box-trim: trim-start;
text-box-edge: ex;
Trim above the x-height and below the alphabetic baseline
To trim above the x-height and below the alphabetic baseline:
text-box-trim: trim-both;
text-box-edge: ex alphabetic;
Trim below the alphabetic baseline only
To trim below the alphabetic baseline only, the following won’t work (things were going so well for a moment, weren’t they?):
text-box-trim: trim-end;
text-box-edge: alphabetic;
This is because the first value is always the mandatory over-edge value whereas the second value is an optional under-edge value. This means that alphabetic isn’t a valid over-edge value, even though the inclusion of trim-end
suggests that we won’t be providing one. Complaints about verbosity aside, the correct syntax would have you declare any over-edge value even though you’d effectively cancel it out with trim-end
:
text-box-trim: trim-end;
text-box-edge: [any over edge value] alphabetic;
What about ideographic glyphs?
It’s difficult to know how web browsers will trim ideographic glyphs until they do, but you can read all about it in the spec. In theory, you’d want to use the ideographic-ink
value for trimming and the ideographic
value for no trimming, both of which aren’t unsupported yet:
text-box-edge: ideographic; /* No trim */
text-box-edge: ideographic-ink; /* Trim */
text-box-edge: ideographic-ink ideographic; /* Top trim */
text-box-edge: ideographic ideographic-ink; /* Bottom trim */
text-box
, the shorthand property
If you’re not keen on the verbosity of text box trimming, there’s a shorthand text-box
property that makes it somewhat inconsequential. All the same rules apply.
/* Syntax */
text-box: [text-box-trim] [text-box-edge (over)] [text-box-edge (under)]?
/* Example */
text-box: trim-both cap alphabetic;
Final thoughts
At first glance, text-box-trim
and text-box-edge
might not seem all that interesting, but they do make spacing elements a heck of a lot simpler.
Is the current proposal the best way to handle text box trimming though? Personally, I don’t think so. I think text-box-trim-start
and text-box-trim-end
would make a lot more sense, with text-box-trim
being used as the shorthand property and text-box-edge
not being used at all, but I’d settle for some simplification and/or consistent practices. What do you think?
There are some other concerns too. For example, should there be an option to include underlines, overlines, hanging punctuation marks, or diacritics? I’m going to say yes, especially if you’re using text-underline-position: under
or a particularly thick text-decoration-thickness
, as they can make the spacing between elements appear smaller.