Letter Spacing is Broken and There’s Nothing We Can Do About It… Maybe

DigitalOcean provides cloud products for every stage of your journey. Get started with $200 in free credit!

This post came up following a conversation I had with Emilio Cobos — a senior developer at Mozilla and member of the CSSWG — about the last CSSWG group meeting. I wanted to know what he thought were the most exciting and interesting topics discussed at their last meeting, and with 2024 packed with so many new or coming flashy things like masonry layout, if() conditionals, anchor positioning, view transitions, and whatnot, I thought his answers had to be among them.

He admitted that my list of highlights was accurate on what is mainstream in the community, especially from an author’s point of view. However, and to my surprise, his favorite discussion was on something completely different: an inaccuracy on how the letter-spacing property is rendered across browsers. It’s a flaw so ingrained on the web that browsers have been ignoring the CSS specification for years and that can’t be easily solved by a lack of better options and compatibility issues.

Emilios’s answer makes sense — he works on Gecko and rendering fonts is an art in itself. Still, I didn’t get what the problem is exactly, why he finds it so interesting, and even why it exists in the first place since letter-spacing is a property as old as CSS. It wasn’t until I went into the letter-spacing rabbit hole that I understood how amazingly complex the issue gets and I hope to get you as interested as I did in this (not so) simple property.

What’s letter spacing?

The question seems simple: letter spacing is the space between letters. Hooray! That was easy, for humans. For a computer, the question of how to render the space between letters has a lot more nuance. A human just writes the next letter without putting in much thought. Computers, on the other hand, need a strategy on how to render that space: should they add the full space at the beginning of the letter, at the end, or halve it and add it on both sides of the letter? Should it work differently from left-to-right (LTR) languages, like English, to right-to-left (RTL) like Hebrew? These questions are crucial since choosing one as a standard shapes how text measurement and line breaks work across the web.

Which of the three strategies is used on the web? Depends on who you ask. The implementation in the CSS specifications completely differs from what the browsers do, and there is even incompatibility between browsers rendering engines, like Gecko (Firefox), Blink (Chrome, Brave, Opera, etc.), and WebKit (Safari).

What the CSS spec says

Let’s backpedal a bit and first know how the spec says letter spacing should work. At the time of writing, letter-spacing:

Specifies additional spacing between typographic character units. Values may be negative, but there may be implementation-dependent limits.

The formal specification has more juice to it, but this one gives us enough to understand how the CSS spec wants letter-spacing to behave. The keyword is between, meaning that the letter spacing should only affect the space between characters. I know, sounds pretty obvious.

So, as the example given on the spec, the following HTML:

<p>a<span>bb</span>c</p>

…with this CSS:

p {
  letter-spacing: 1em;
}

span {
  letter-spacing: 2em;
}

…should give an equal space between the two “b” letters:

Letter spacing on paper. The letter spacing is only applied between the letters "b"s

However, if we run the same code on any browser (e.g., Chrome, Firefox, or Safari), we’ll see the spacing isn’t contained between the “b” letters, but also at the end of the complete word.

Letter spacing on browsers. The letter spacing is applied between the letters "b"s and on the right-hand side of the last letter "b"

What browsers do

I thought it was normal for letter-spacing to attach spacing at the end of a character and didn’t know the spec said otherwise. However, if you think about it, the current behavior does seem off… it’s just that we’re simply used to it.

Why would browsers not follow the spec on this one?

As we saw before, letter spacing isn’t straightforward for computers since they must stick to a strategy for where spacing is applied. In the case of browsers, the standard has been to apply an individual space at the end of each character, ignoring if that space goes beyond the full word. It may have not been the best choice, but it’s what the web has leaned into, and changing it now would result in all kinds of text and layout shifts across the web.

This leaves a space at the end of elements with bigger letter spacing, which is somewhat acceptable for LTR text, but it leaves a hole at the beginning of the text in an RTL writing mode.

The issue is more obvious with centered text, where the ending space pushes the text away from the element’s dead center. You’ve probably had to add padding on the opposite side of an element to make up for any letter-spacing you’ve applied to the text at least one time, like on a button.

As you can see, the blue highlight creates a symmetrical pyramid which our text sadly doesn’t follow.

What’s worse, the “end of each character” means something different to browsers, particularly when working in an RTL writing mode. Chrome and Safari (Blink/WebKit) say the end of a character is always on the right-hand side. Firefox (Gecko), on the other hand, adds space to the “reading” end — which in Hebrew and Arabic is the left-hand side. See the difference yourself:

Side-by-side comparison of letter spacing on Gecko and Blink/Webkit

Can this be fixed?

The first thought that comes to mind is to simply follow what the spec says and trim the unnecessary space at the ending character, but this (anti) solution brings compatibility risks that are simply too big to even consider; text measurement and line breaks would change, possibly causing breakage on lots of websites. Pages that have removed that extra space with workarounds probably did it by offsetting the element’s padding/margin, which means changing the behavior as it currently stands makes those offsets obsolete or breaking.

There are two real options for how letter-spacing can be fixed: reworking how the space is distributed around the character or allowing developers an option to choose where we want the ending space.

Option 1: Reworking the space distribution

The first option would be to change the current letter-spacing definition so it says something like this:

Specifies additional spacing applied to each typographic character unit except those with zero advance. The additional spacing is divided equally between the inline-start and -end sides of the typographic character unit. Values may be negative, but there may be implementation-dependent limits.

Simply put, instead of browsers applying the additional space at the end of the character, they would divide it equally at the start and end, and the result is symmetrical text. This would also change text measurements and line breaks, albeit to a lesser degree.

Letter spacing with the symmetrical fix. The letter spacing is equally applied around the letters "b"s

Now text that is center-aligned text is correctly aligned to the center:

Different examples of letter spacing being distributed between letters and achieving a symmetrical look

Option 2: Allowing developers an option to choose

Even if the offset is halved, it could still bring breaking layout shifts to pages which to some is still (rightfully) unacceptable. It’s a dilemma: most pages need, or at least would benefit, from leaving letter-spacing as-is, while new pages would enjoy symmetrical letter spacing. Luckily, we could do both by giving developers the option to choose how the space is applied to characters. The syntax is anybody’s guess, but we could have a new property to choose where to place the spacing:

letter-spacing-justify: [ before | after | left | right | between | around];

Each value represents where the space should be added, taking into account the text direction:

  • before: the spacing is added at the beginning of the letter, following the direction of the language.
  • after: the spacing is added at the end of the letter, following the direction of the language.
  • left: the spacing is added at the left of the letter, ignoring the direction of the language.
  • right: the spacing is added at the right of the letter, ignoring the direction of the language.
  • between: the spacing is added between characters, following the spec.
  • around: the spacing is divided around the letter.

Logically, the current behavior would be the default to not break anything and letter-spacing would become a shorthand for both properties (length and placing).

letter-spacing: 1px before;
letter-spacing: 1px right;
letter-spacing: 1px around;

letter-spacing: 1px;
/* same as: */
letter-spacing: 1px before;

What about a third option?

And, of course, the third option is to leave things as they are. I’d say this is unlikely since the CSSWG resolved to take action on the issue, and they’ll probably choose the second option if I had to bet the nickel in my pocket on it.

Now you know letter-spacing is broken… and we have to live with it, at least for the time being. But there are options that may help correct the problem down the road.