In my recent article about CSS underline bugs in Chrome, I discussed text-decoration-thickness
and text-underline-offset
, two relatively new and widely-supported CSS properties that give us more control over the styling of underlines.
Let me demonstrate the usefulness of text-decoration-thickness
on a simple example. The Ubuntu web font has a fairly thick default underline. We can make this underline thinner like so:
:any-link {
text-decoration-thickness: 0.08em;
}
/explanation Throughout this article, I will use the :any-link
selector instead of the a
element to match hyperlinks. The problem with the a
tag as a selector is that it matches all <a>
elements, even the ones that don’t have a href
attribute and thus aren’t hyperlinks. The :any-link
selector only matches <a>
elements that are hyperlinks. Web browsers also use :any-link
instead of a
in their user agent stylesheets.
Hover underlines
Many websites, including Google Search and Wikipedia, remove underlines from links and only show them when the user hovers a link. Removing underlines from links in body text is not a good idea, but it can make sense in places where links are more spaced apart (navigation, footer, etc.). With that being said, here’s a simple implementation of hover underlines for links in the website’s header:
header :any-link {
text-decoration: none;
}
header :any-link:hover {
text-decoration: underline;
}
But there’s a problem. If we tested this code in a browser, we’d notice that the underlines in the header have the default thickness, not the thinner style that we declared earlier. Why did text-decoration-thickness
stop working after we added hover underlines?
Let’s look at the full CSS code again. Can you think of a reason why the custom thickness
doesn’t apply to the hover underline?
:any-link {
text-decoration-thickness: 0.08em;
}
header :any-link {
text-decoration: none;
}
header :any-link:hover {
text-decoration: underline;
}
The reason for this behavior is that text-decoration
is a shorthand property and text-decoration-thickness
its associated longhand property. Setting text-decoration
to none
or underline
has the side effect of re-initializing the other three text decoration components (thickness
, style
, and color
). This is defined in the CSS Text Decoration module:
The
text-decoration
property is a shorthand for settingtext-decoration-line
,text-decoration-thickness
,text-decoration-style
, andtext-decoration-color
in one declaration. Omitted values are set to their initial values.
You can confirm this in the browser’s DevTools by selecting one of the hyperlinks in the DOM inspector and then expanding the text-decoration
property in the CSS pane.
In order to get text-decoration-thickness
to work on hover underlines, we’ll have to make a small change to the above CSS code. There are actually multiple ways to achieve this. We could:
- set
text-decoration-thickness
aftertext-decoration
, - declare the thickness in the
text-decoration
shorthand, or - use
text-decoration-line
instead oftext-decoration
.
Choosing the best text-decoration option
Our first thought might be to simply repeat the text-decoration-thickness
declaration in the :hover
state. It’s a quick and simple fix that indeed works.
/* OPTION A */
header :any-link {
text-decoration: none;
}
header :any-link:hover {
text-decoration: underline;
text-decoration-thickness: 0.08em; /* set thickness again */
}
However, since text-decoration
is a shorthand and text-decoration-thickness
is its associated longhand, there really should be no need to use both at the same time. As a shorthand, text-decoration
allows setting both the underline itself and the underline’s thickness, all in one declaration.
/* OPTION B */
header :any-link {
text-decoration: none;
}
header :any-link:hover {
text-decoration: underline 0.08em; /* set both line and thickness */
}
If this code looks unfamiliar to you, that could be because the idea of using text-decoration
as a shorthand is relatively new. This property was only subsequently turned into a shorthand in the CSS Text Decoration module. In the days of CSS 2, text-decoration
was a simple property.
Unfortunately, Safari still hasn’t fully caught up with these changes. In the WebKit browser engine, the shorthand variant of text-decoration
remains prefixed (-webkit-text-decoration
), and it doesn’t support thickness
values yet. See WebKit bug 230083 for more information.
This rules out the text-decoration
shorthand syntax. The above code won’t work in Safari, even if we added the -webkit-
prefix. Luckily, there’s another way to avoid repeating the text-decoration-thickness
declaration.
When text-decoration
was turned into a shorthand, a new text-decoration-line
longhand was introduced to take over its old job. We can use this property to hide and show the underline without affecting the other three text decoration components.
/* OPTION C */
header :any-link {
text-decoration-line: none;
}
header :any-link:hover {
text-decoration-line: underline;
}
Since we’re only updating the line
component of the text-decoration
value, the previously declared thickness
remains intact. I think that this is the best way to implement hover underlines.
Be aware of shorthands
Keep in mind that when you set a shorthand property, e.g., text-decoration: underline
, any missing parts in the value are re-initialized. This is also why styles such as background-repeat: no-repeat
are undone if you set background: url(flower.jpg)
afterwards. See the article “Accidental CSS Resets” for more examples of this behavior.
Can’t understand why can’t we set text-decoration-thickness to 0. Why there is a limit?
The minimum thickness is “one device pixel”. I guess that decision was made for simplicity.