12
Links
Default underlines clip descenders at small sizes and look heavy at large ones. Three properties fix both.
6 min read
Default link underlines run through descenders. The g in "login" has a line slicing through its tail. At 0.875rem the underline is as thick as the stroke on the letter itself. On dark backgrounds, it almost disappears.
You've noticed this. You've either removed the underline entirely (bad for accessibility) or left it and hoped no one looks closely. There's a third option.
Link Underlines
Toggle to improved. The underlines drop below the descenders, thin out to 1px, and mute to 40% opacity. On hover they return to full color.
The Rule
Set text-underline-offset: 0.15em, text-decoration-thickness: 1px, and text-decoration-color using color-mix().
a {
text-underline-offset: 0.15em;
text-decoration-thickness: 1px;
text-decoration-color: color-mix(
in srgb,
currentColor 40%,
transparent
);
transition: text-decoration-color 180ms ease-out;
}
a:hover {
text-decoration-color: currentColor;
}a {
text-underline-offset: 0.15em;
text-decoration-thickness: 1px;
text-decoration-color: color-mix(
in srgb,
currentColor 40%,
transparent
);
transition: text-decoration-color 180ms ease-out;
}
a:hover {
text-decoration-color: currentColor;
}text-underline-offset moves the underline below the baseline. The em unit means it scales with font size, so it stays proportional on headings and captions without needing override rules. At 0.15em, it clears most descenders on common web fonts.
text-decoration-thickness: 1px keeps the line consistent regardless of font size. Default underlines scale with font size in some browsers, which makes large headings look underlined with a ruler.
text-decoration-color with color-mix() gives a muted underline at rest that brightens on hover. The link is clearly linked without the underline competing with the text.
Common Mistake
Setting text-decoration: none on body links to avoid descender clipping. This removes the visual affordance that text is a link, which breaks WCAG 1.4.1 (use of color).
Underline
Start with both sliders at zero. The underline slices through descenders. Drag offset toward 0.15em — the line drops below the baseline and clears the g and y. Drag thickness down to 1px — the line stops competing with the letterforms. The pass indicator turns green when both values are in the right range.
Skip-ink
text-decoration-skip-ink: auto is the browser default and already does the right thing: the underline breaks around descenders. Don't override it.
/* Don't do this — it forces the underline through descenders */
a {
text-decoration-skip-ink: none;
}/* Don't do this — it forces the underline through descenders */
a {
text-decoration-skip-ink: none;
}Navigation links
Nav links usually shouldn't have underlines at rest. The active state benefit is different: underlines communicate "you are here" more clearly than background color alone.
.nav-link {
text-decoration: none;
}
.nav-link[aria-current="page"] {
text-decoration: underline;
text-underline-offset: 0.2em;
text-decoration-thickness: 2px;
text-decoration-color: currentColor;
}.nav-link {
text-decoration: none;
}
.nav-link[aria-current="page"] {
text-decoration: underline;
text-underline-offset: 0.2em;
text-decoration-thickness: 2px;
text-decoration-color: currentColor;
}All three properties are supported in Chrome 87+, Firefox 70+, Safari 12.1+.