v154 · css · text decoration

CSS text-decoration-inset

An underline runs the full width of its text run, and there was no way to say otherwise. text-decoration-inset pulls the ends in — or pushes them out — with a length, a percentage, or two values for the start and end independently. Underline animations stop being background gradients pretending to be underlines.

concepts

  1. Inset lab

    Both ends of a real underline, driven independently, with the drawn width measured off the rendered box. Negative values extend past the text, which is where the margin-note and callout patterns come from.

  2. The reveal effect

    The hover underline everyone builds with a background gradient, built instead with a real text-decoration. Side by side with the gradient version, so you can see what the gradient gets wrong — wrapping, descenders, and text-decoration-skip-ink.

  3. Beyond underlines

    The property applies to every decoration line. Insetting a line-through makes a strike that stops short of the punctuation; insetting an overline gives a header rule that aligns to the text rather than the box.

why it shipped

Animated underlines are one of the most-implemented effects on the web and almost none of them are underlines. They are a background-image gradient positioned near the baseline, or a pseudo-element with a border, because those are the only things whose width you could control. Both lose everything the real decoration knows: where the baseline is at this font size, how to skip descenders, what happens when the text wraps to two lines, and how to follow the text colour.

text-decoration-inset makes the real thing controllable. One value insets both ends, two values set start and end separately, and negative values extend the line beyond the text.

the API

/* Pull both ends in by 4px. */
a { text-decoration-inset: 4px; }

/* Start and end independently. */
.pull-quote { text-decoration-inset: 0 2em; }

/* Percentages resolve against the text run. Animate to zero for a
   reveal that is a real underline the whole way. */
a { text-decoration-inset: 100% 0; transition: text-decoration-inset .25s; }
a:hover { text-decoration-inset: 0; }

references