Fixing link underline alignment with text-decoration-inset

I recently worked with a boutique owner who cared about typography more than any client I have had. They sent me a screenshot of their new landing page with the link underlines circled. The underlines ran a hair past the vertical stems of the letters, maybe 2px of misalignment. To them it was a “total mess.”

My first move was the one I have made a thousand times: kill the native underline with text-decoration: none, then rebuild it as an ::after with a custom background. That held up until the client bumped the font size on mobile and the pseudo-element drifted out of position. Then they added a second line of text and the whole thing collapsed, because a pseudo-element has no native sense of where a line break falls. Maintaining that across an entire WooCommerce shop was not something I wanted to sign up for.

Using text-decoration-inset instead

The fix is a fairly new CSS property called text-decoration-inset. It behaves like padding, except it applies to your text decorations rather than the box. Give it a value and the underline gets clipped from the left or the right side, with no extra spans and no absolute positioning. There is a good write-up of the vertical misalignment problem it solves over at css-tricks.com.

The syntax takes two values, one for the left inset and one for the right. Use em units so the effect scales with your font size. Hardcoded pixels in a responsive WordPress theme will come back to bite you the first time a breakpoint changes the type scale.

/* bbioon-custom-link-styles */
.bbioon-fancy-link {
  text-decoration-line: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.2em;
  /* Clipping the underline 0.5em from each side */
  text-decoration-inset: 0.5em 0.5em;
}

Animating the underline natively

Transitions are where the property earns its keep. A “shooting star” underline normally sends you back to pseudo-elements and transform: scaleX(). With text-decoration-inset you animate the inset values directly, and the underline stays tied to the baseline metrics of the font while it moves. That is why it looks better than the pseudo-element version.

.bbioon-animated-link {
  text-decoration: underline;
  text-decoration-inset: 0.1em 0.1em;
  transition: text-decoration-inset 0.3s ease-in-out;
}

.bbioon-animated-link:hover {
  /* Underline shrinks toward the center on hover */
  text-decoration-inset: 2em 2em;
}

Browser support

Here is the catch. Support today is basically Firefox 146 and up. Try it in Chrome and you get nothing at all. I still use it, because this is exactly the kind of thing progressive enhancement is for. Most visitors see a normal, clean underline. The ones on a browser that implements the property get the tighter version.

Balancing a designer’s demands against code you still want to maintain gets messy fast. If you are tired of debugging someone else’s CSS and you just want the site to work and look right while it does, drop my team a line. We have probably seen your particular mess before.

What to take away

  • Native text-decoration-inset is less code to maintain than a pseudo-element underline, and it performs better.
  • Use em units so the decoration scales with the typography.
  • New CSS is fine to ship as long as you treat it as an enhancement rather than a requirement.
  • Clients who care about typography do notice details at this level.
author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.