Adobe

decor

Web Platform Team Blog

Making the web awesome

One Weird Trick to Baseline-Align Text

I’m writing this post because I hope I’m wrong. I have found a silly solution to a common typographic problem, and I hope there’s a better way to do what I want.

The problem is baseline-aligning text. I want to position a character’s baseline at a particular spot, in a way that works with all browsers, fonts and font sizes. The reason I want to do this is for drop cap positioning. A drop cap should have its baseline aligned with the lowest line of regular text it’s next to. I can estimate where that baseline is, so the trick is figuring out how to get the drop cap’s baseline to match.

Let’s simplify the problem. Say I have a 100x100px div with a border. The div contains a span with a single letter, and I want the baseline of that letter to sit on the bottom edge of the border.

<div>
  <span>T</span>
<div>
div {
  width: 100px;
  height: 100px;
  border: thin black solid;
}
span {
  font-size: 100px;
  line-height: 100px;
  background-color: #9BBCE3;
}

I’ve started with a font-size and line-height of 100px. This won’t do what I want, because in CSS line-height is measured from the ascender to the descender. You might think (as I initially did) that all I’ll need to do is find the descender height and use a margin to make the text sit down on the border. The descender height will vary from font to font, so whatever solution might have to be font-specific.

But I quickly found that not only is the baseline position dependent on the font, it also varies by browser. Here’s the result of the above in Firefox, Chrome and IE:

Default letter 'T' rendered in Firefox, Chrome and IE

Firefox, Chrome and IE rendering

Firefox and Chrome are both on MacOS using the same font, but the baseline is much higher in Firefox than Chrome. IE is using a different font file for the default font, but manages to match Chrome in this instance. At this point I thought there might be a bug in Firefox, or that I might just need to conjure a Firefox-specific offset to get cross-browser baseline alignment.

But it’s not as simple as that. The variations in browser baseline positioning are compounded by font changes. Here’s the result with font-family set to Adobe Garamond Pro (the same font file used in each browser):

Garamond letter 'T' rendered in Firefox, Chrome and IE

Firefox, Chrome and IE rendering

Here Firefox and Chrome are merely one pixel apart, and IE has decided to go its own way. I tried a few other fonts and found a mishmash of results – the browsers do not agree on where to place text baselines, and have no pattern across fonts that I could discern. Dealing with descender height in order to place a baseline at a particular point appears both font-specific and browser-specific in a way I couldn’t untangle.

By the way, I originally added the background-color to the span to see if that would illuminate why the results were so different. But as you can see that’s another cross-browser inconsistency. Chrome isn’t even consistent with itself on the extent of a span’s background when I change the font. This is a separate issue, and not terribly relevant to the baseline alignment problem.

So, what to try next? I know from past experience that inline images use the bottom edge of the image as the baseline. Perhaps there’s something useful about replaced elements or inline blocks that I can exploit. It turns out that if I add an empty inline-block strut to the div, I can use it to position the baseline for the entire line.

<div>
  <span class="letter">T</span>
  <span class="strut"></span>
<div>
div {
  width: 100px;
  height: 100px;
  border: thin black solid;
}
.letter {
  font-size: 100px;
  line-height: 0px;
  background-color: #9BBCE3;
}
.strut {
  display: inline-block;
  height: 100px;
}

The baseline of the strut is placed at its bottom margin edge, which is determined solely by the height of the strut in this case. And if the letter span’s line-height is smaller than the strut’s height, the letter’s baseline will move to match. Here I’ve set the line-height of the letter’s span to 0px to make sure it’s smaller. You can check out the result in any browser in this pen (try changing the font and/or the font-size):

The strut has to be empty. At first I had no idea why, but my colleague @sgalineau found the reference in the very last sentence of the CSS 2.1 section on line height. There’s a special case for an inline-block with no line boxes that gives us this useful result.

As I mentioned, my specific reason for all of this was drop cap positioning, but there are plenty of other reasons to want to align a text baseline to something else. Is this inline-block strut trick the best we have to work with in CSS? Can someone show me a better way? If so, leave a comment here or contact me at @adobeweb and/or @alanstearns.

13 Comments

  1. August 13, 2014 at 10:07 am, Alex Lande said:

    Very cool. This isn’t particularly insightful, but the strut can be replaced with a pseudo-element, which is a bit cleaner.

    • August 13, 2014 at 10:17 am, Alan Stearns said:

      Yes. For our dropcap code, it’s easier to insert an element strut. But if you want to use pure CSS (and have a spare pseudo-element) you can make the strut there too. I have an example that uses a pseudo-element to achieve a first-baseline offset.

  2. August 13, 2014 at 10:39 am, Den said:

    Well, believe it or not, I was the One who was facing this weird typographic problem and thanks to your tutorial, it is now fixed.

    • August 13, 2014 at 10:41 am, Alan Stearns said:

      That’s great! Can I ask about the purpose you put this to?

  3. August 17, 2014 at 12:10 pm, GreLI said:

    While not in any spec yet, there is an iniciative in W3 CSS working group about drop caps:
    http://lists.w3.org/Archives/Public/www-style/2014May/0211.html

    • August 17, 2014 at 12:30 pm, Alan Stearns said:

      Spec work has already started. You can see it in the editor’s draft of CSS-Inline. The drop cap code we’re working on is a response to the interest shown in the CSSWG. Hopefully we’ll have something to show for this soon.

  4. August 18, 2014 at 6:57 am, Andrey Donovan said:


    div {
    width: 100px;
    height: 100px;
    border: thin black solid;
    }
    .letter {
    font-size: 100px;
    line-height: 0px;
    background-color: #9BBCE3;
    }
    .letter:after {
    display: inline-block;
    height: 100px;
    }

    • August 25, 2014 at 11:37 pm, Luca R. said:

      content: ""; in letter:after or it won’t work.

  5. August 22, 2014 at 3:22 am, Sebastian said:

    I think using generated content is a good idea.

    BTW 1: line-height on the text is not required. It always works the same – independent from the actual line height.

    BTW 2: Better use 100% for the height of the helper element.

    Like:


    div {
    width: 100px;
    height: 100px;
    border: thin black solid;
    margin: 10px;
    }

    .letter {
    font-size: 60px;
    background-color: #9BBCE3;
    }

    .letter::after {
    content: "";
    height: 100%;
    display: inline-block;
    }

    • August 22, 2014 at 9:47 am, Alan Stearns said:

      You do have to make sure that the glyph’s line-height isn’t too much larger than the strut’s. Try setting the .letter line-height to 2 (or 200px) in the codepen to see what I mean.

      You’re correct that 100% works great for the simplified example. In other cases you might not be trying to match the baseline to the bottom of its parent element.

  6. August 24, 2014 at 2:10 am, Lewis Cowles said:

    So how about putting the class to the div, and using :first-letter pseudo selector…

    Here it is with the text in a p tag, rather than a div; btw IMHO a div is not fit for purpose for this, due to the rise of the semantic web, I think Adobe would be looking into the semantic web… Oh and as for all browsers, use a polyfill or better yet target an audience who does not have a crap browser. The official sales stats always quote missing out on ie6,7,8 users, but I have yet to see sales figures showing significant purchases from these users, and lets face it if they cant or wont buy a new PC, there is not much point marketing to them on this is there?

    http://jsfiddle.net/LewisCowles1986/mtb1wmga/embedded/result/

  7. August 24, 2014 at 5:29 am, ben said:

    On same experiment approach, check this tiny framework http://b4d455.fr/basel/ (ctrl+G to show grid)

  8. August 24, 2014 at 12:29 pm, Daniel Wabyick said:

    Awesome post! Is typical CSS black magic, but very cool. Using this technique, I’m interested to see if I can get x-height alignment right …