CSS Underlines Suck

January 31, 2015

Rebuilding my personal website, I was reminded of just how bad text underlining support is in CSS. After reviewing countless blog posts, Stack Overflow answers, and CSS design articles, I’ve put together a (hopefully useful) survey of various hacks and workarounds that you can use to make underlining suck less.

Just want to know what to use? Skip to the bottom of this article.

Writing this post I was reminded of an popular earlier article written by Marcin Wichary of Medium, where he attempted to craft the “perfect” cross-browser underline. He wrote:

So, the ideal technological solution would allow us to:

If you haven’t read that article yet, do so now. I’ll wait.

Throughout this article, I’ll refer back to the Medium one, but this article covers more techniques, including libraries developed in response to Marcin’s post.

The W3C has a spec that solves some of this, but browser support is still practically non-existent. Firefox has support with a prefix, and Chrome has support, but only if you have the experimental features enabled.

To quote a developer from that thread,

“So to a user, it doesn’t work. An for a developer, using it is pointless, because noone will see it.”

The consensus seems to be that if you actually want to have nice-looking underlines, you’re forced to resort to one of a number hacks. Each includes their own set of advantages and disadvantages.

caniuse.com’s results for text-decoration, as of writing.


The obvious solution is to reuse the already similar border-bottom property.

a {
    text-decoration: none;
    border-bottom: 0.1em dashed #1AC63A;
    padding-bottom: .1em;

This lets us achieve styling like

The quick brown fox jumped over the lazy dog.



What comes ::after border-bottom?

Scouring for alternatives, I came across this great blog post from Artsy’s engineering team. They used the ::after pseudo-element to render the line, and then were able to use a combination of a 1em height and variable margin values to finely control the line’s position.

For convenience, I’m reprinting their solution verbatim here1.

a {
    display: inline-block;
    position: relative;
a::after {
    content: '';
    position: absolute;
    left: 0;
    display: inline-block;
    height: 1em;
    width: 100%;
    border-bottom: 1px solid;
    margin-top: 5px;




Upon posting this to Reddit, /u/omgmog mentioned

There’s also the box-shadow method: http://codepen.io/omgmog/pen/XJeWwG

As it turns out, this was briefly mentioned in the Medium article, and I glossed over it2. Unfortunately, this carries the same limitations as our original border-bottom, approach, without the advantage of adjusting style.


Roughly the same advantages as border-bottom.



In his medium article mentioned at the beginning of this article, Marcin Wichary ended up choosing to implement underlines with transparent linear-gradient backgrounds. Multiple implementations of this exist on codepen.



linear-gradient, Take Two

When I first posted this, I assumed there was no way to do dashed or underlined styles with linear-gradient. Turns out I was mistaken.

/u/Disgruntled__Goat of Reddit pointed out:

I’m sure linear gradient could do dotted/dashed underline, just have a second gradient going horizontally.

I implemented this idea on codepen, and ended up with:



A Quick Descent Into Descenders

Descenders are the lines at the bottom of your p’s and q’s. When an underline runs through descenders, it can muddy the text and reduce readability.

The bottom of the ‘p’ in Sphinx is a descender. Source.

In an effort to improve the legibility of descenders, Safari 8 and up implement spacing where descenders intersect with underlines. The W3C also has a spec for it, called text-decoration-skip: ink.

text-decoration-skip: ink. Source.

However, as with everything in typography, this is hotly debated, and it’s possibly that the stylistic difference between words with lots of descenders, like typography and those without may detract from readability.


A solution for implementing descender-aware underlines is mentioned in Marcin Wichary’s post, where he mentions

My colleague Dustin found a way, and it’s as ingenious as impractical — applying a white CSS text shadow or a text stroke to paint over the underline and simulate a gap between the underline and the text.

SmartUnderline is a fairly complete implementation of this idea, and eager.io’s blog post on it sports a really cool demo of how it work.



From the eager.io’s blog post:


Underline.js is a unique, and currently experimental, approach that uses Canvas to render links with gaps for descenders,




Putting all this information together, we can form a matrix of solutions and workarounds. Every solution has their own set of problems, but given your project requirements, a partial solution may do what’s needed.

Solution Browsers Position Style Color Thickness Descenders Multiline Performant
W3C Spec Firefox only Yes Yes Yes Yes Yes Yes Yes
border-bottom Even IE6 Partial Yes Yes Yes No Yes Yes
::after Trick Modern Yes Yes Yes Yes No No Yes
box-shadow Modern Partial No Yes Yes No Yes Yes
linear-gradient Modern Yes No Yes Yes No Yes Yes
Two Gradients Modern Yes Dashed Yes Yes No Yes Yes
SmartUnderline Modern Yes Not yet Yes Yes Yes Yes Meh
Underline.js Modern Yes Not yet Yes Yes Yes Yes Hell no

  1. Please don’t sue.↩︎

  2. To be fair, it’s a long article, and it wasn’t a very noteworthy point.↩︎

The views expressed on this site are my own and do not reflect those of my employer.