Skip to main content

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 . 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 that text is a link, which breaks (use of color).

Underline

offsettext-underline-offset: 0.00em — cuts through descenders
thicknesstext-decoration-thickness: 3px — too heavy

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+.