Relative Font Weights Considered Harmful
Not exactly, but who can resist writing a "considered harmful" article when you can get away with it?
The real harm is that you can very easily conceal the semantics conveyed by font-weight
depending on the font that's rendered, which is not always in your control. This all depends on how you define the base weight to which your relative values refer, and (1) whether that base weight is actually available in the rendered font and (2) which value is substituted if it isn't.
So let's unpack that.
Font Weight
Font weights are easy to understand when we deal in simple terms like normal
and bold
. Font weight on the web is traditionally a binary, more like bold and not bold. But these keywords actually correspond to numerical values and will resolve to another weight if that numerical value is unavailable:
Keyword Declaration | Numerical Declaration | |
---|---|---|
font-weight: normal; | = | font-weight: 400; |
font-weight: bold; | = | font-weight: 700; |
Fonts can come in any one of up to nine weights, numbered in whole hundreds: 100–900. You've doubtless seen fonts with named weights in different contexts, like "thin," "medium," "extra bold," and "black." Each of these would align with one of the nine numbered weights.
While most fonts only have a small handful of available weights, there are some that run the gamut, all the way from 100 to 900, and this is becoming increasingly common with the ubiquity of webfonts. (This is another problem in itself, bloating sites for users and wasting bandwidth.)
1 step forward, X steps back
If the rendered font lacks the corresponding weight, the browser will select the nearest weight according to a simple algorithm.
An easy way to think about it is this:
normal
wants to be normal — or at least not boldbold
wants to be bold — or at least not thin
Normal will err on the side of light, and if bold can't be bold, it will try it's damnedest anyway.
The range for normal
is 400–500. Anything higher — 600 and up, even though 700 is the sweet spot — is in the bold
range. And while it doesn't have a base name, 100–300 can be considered light.
For normal text, if the rendered font lacks weight 400, it will go to 500. If that's not available, it really doesn't want to be bold, so it has nowhere to go but down, to 300. And so on. It will go bold if it must, but it wants to be not bold.
Likewise, for bold text, if the rendered font lacks weight 700, it will err on the side of boldness: 800, then 900, and then it will drop its shoulders in resignation and work backwards taking the first available weight that is the least light.
That's all well and good. But what happens with the relative keywords, lighter
and bolder
, and why do I tease that they're "considered harmful?"
Be careful what you ask for
Let's say you ask for a specific weight in your CSS file:
p {
font-weight: 300;
}
p strong {
font-weight: stronger;
}
This may seemlike an unfair example, but things like this happen, and you'd be excused for thinking that strong
text would be appear bolder than the rest of the paragraph.
It would appear stronger if the rendered font had 300 available along with any stronger weight. But what if it doesn't?
The lighter
and bolder
keywords respond to the inherited weight, and can resolve to 100, 400, 700, or 900 depending on the declared weight. So, for example, our example with a paragraph of font-weight: 300
containing a strong
element with font-weight: bolder;
is the same as the following:
p {
font-weight: 300;
}
p strong {
font-weight: 400;
}
But if we change the 300
to 400
, then bolder
becomes equivalent to 700
. That's not something you'd ever want to keep track of and certainly something you'd never be able to keep up with.
But let's say you go ahead and define your font-weight at 300, and it isn't available. The text will render at the nearest available weight according to the selection algorithm. If the rendered font is Arial on a common Windows machine, then the available weight is probably 500. The strong
declaration of bolder
means "bolder than 300", and thus "400", but it, too, will render at 500, following the defined algorithm for resolving available font weights.
p {
font-weight: 300;
/* Unavailable: resolves to 500 */
}
p strong {
font-weight: 400;
/* Unavailable: resolves to 500 */
}
In this case, the visual semantics are lost.
So if that matters to you (and it should), then tread carefully here. At the very least, you want to be very thoughtful about choosing weights for web fonts, and how well your weight declarations will fall back in their absence.