CSS Underlines
January 31, 2015
Update: This article is quite outdated, and browser support
for text-decoration-style
has since improved.
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 better.
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.
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.
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. - 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.
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
.
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 |
The views expressed on this site are my own and do not reflect those of my employer.