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: change the width of the line (with additional half-pixel/retina support),

change the distance from the text,

change the color (even if just to simulate thinner width by using lighter grays instead of black),

clear the descenders,

(perhaps) have a separate style for visited links.

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,

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.

border-bottom

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.

Pros

padding-bottom can be used to lower the underline.

can be used to lower the underline. Color, thickness, and line style can all be adjusted independently of the font.

Cons

While padding-bottom can lower the underline, there’s no easy way to raise it. The line must be below the descenders (more on this later). While fine for large text, this works poorly for links embedded in paragraphs.

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

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 ; }

Pros

Full control of line positioning, plus all the benefits of the earlier border-bottom approach.

Cons

The link must now be inline-block , which, in addition to somewhat reduced browser compatibility (meh), and…

The link can no longer be split across multiple lines. This means left-aligned text for log links may become jagged, and justified text may result in poor word spacing.

box-shadow

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 it . Unfortunately, this carries the same limitations as our original border-bottom , approach, without the advantage of adjusting style.

Pros

Roughly the same advantages as border-bottom.

Cons

Less browser support than border-bottom .

. Same problems as border-bottom .

. No way to adjust line style.

linear-gradient

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.

Pros

Works well across multiple lines.

Full control of color, width, and positioning; even if a bit finicky.

Cons

No support for different line styles (dashed, dotted, etc).

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:

Pros

Everything about linear-gradient, plus dotted and dashed styles

Cons

You need to know the background color of the containing element, and it needs to be a solid color.

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.

SmartUnderline

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.

Pros

Really cool.

Cons

From the eager.io’s blog post:

It requires that the text be on top of a solid background color.

It makes an assumption about selection color, which is OS- and browser-dependent.

On mobile and older browsers, it may have minor performance penalties. In our testing, we didn’t find much if any impact, especially since the text-shadows don’t use any spread value. But since you’re drawing new things that didn’t used to be there, it’s worth noting.

Underline.js

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

Pros

The most flexible solution here, as it’s able to draw arbitrary pixels.

Animations! Wait, maybe that’s a bad thing…

Cons

The most complicated solution here.

Older browsers don’t support canvas.

Mind-bogglingly slow.

Still experimental.

Overview

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