Icons and Type

As of late, I’ve been thinking about icons and type.

Here’s the example HTML I’m working with here:

<div><i class="icon"></i>cases</div>

Now, that div could be a button, it could be anything. That i could be an image, it could be an svg. Essentially, it’s something that is icon-like, set to display: inline-block. My goal is to center it next to the text beside it.

Let’s take a look at this first example:

Icon next to lowercase text
Icon next to lowercase text

Here’s the CSS I’m using to position the icon next to the text:

.icon { 
  width: 1ex;
  height: 1ex;
  display: inline-block; 
  background-color: salmon;
  vertical-align: middle;
}

The key part here is vertical-align: middle. Instead of resting the icon on the baseline, as it would be default, I’m centering the image with the text.

The reason for this becomes evident when I go with a larger image:

Larger icon next to lowercase text
Larger icon next to lowercase text

Awesome.

So, what’s the problem?

Of course, rarely is copy conveniently all lowercase. Usually it’s title case and full of ascenders, sometimes it’s all caps.

Icon next to title case text

Icon next to uppercase text

Not so awesome.

In this case, aligning the icon to the middle isn’t quite what I want. None of the usual vertical-align values do the trick, though. (Except in cases where we get lucky and the image height just happens to be at the right size to use text-bottom, baseline, or text-top.)

Thankfully, I can adjust the alignment manually.

.icon { 
  vertical-align: -.25ex; 
}
Icon aligned properly next to title text
Icon aligned properly next to title text

Ex Unit

In researching this, I came across the ex unit. I may have already known about it but usually when I want to size relative to type, I’ll use the em or rem unit. The ex unit can be useful because it’s based on the x-height—the height of the letter X.

I decided to use the ex unit for sizing the icon. I like that the icon size is relative to the type. I can change the font size or change the font family and the icon will match the x-height. (Which, with the first example of lowercase type works really well.)

Sadly, most icons at smaller resolutions will look blurry if not aligned to their designed pixel dimensions. Using ex units for icon sizing probably isn’t all that practical. (For example, Arial versus Helvetica will render the icon at different sizes.)

But I digress.

Back to Px

Using pixel units for the icon, none of vertical-align options work “out of the box”. I’ll go go back to vertical-align: middle to, at least, align to the middle of the type, regardless of font size.

.icon { 
  width: 16px;
  height: 16px;
  display: inline-block; 
  background-color: salmon;
  vertical-align: middle;
}

I need one more piece. The icon is currently aligned to the middle of the text including ascenders and descenders (or aligned to the middle of the x-height, I didn’t actually confirm which but close enough).

To align to the middle of the x-height plus the height of the ascenders, I need to move the icon up.

Using Translate

With one extra line of code*, I used em units to tweak the alignment. Increasing or decreasing the font size will keep the icon centered due to the em-based translate.

.icon { 
  width: 16px;
  height: 16px;
  display: inline-block; 
  background-color: salmon;
  vertical-align: middle;
  transform: translateY(-.1em);
}

Sweet, everything aligns!

* One line? I still have browser prefixes to deal with for now, so not quite one line…

Using Top

If we don’t want to deal with browser prefixes, we can also use position:relative and top to adjust accordingly.

.icon { 
  width: 16px;
  height: 16px;
  display: inline-block; 
  background-color: salmon;
  vertical-align: middle;
  position:relative;
  top:-.1em;
}

Recap

Using ex units to size and position the icon offers lots of flexibility but will likely result in blurry icons at smaller sizes. (SVGs or hi-res images on higher res displays may make this more of a moot point in the future.)

Using px units to size the icon and then using em units to adjust the position of the icon results in a more predictable situation (at least, at default browser settings for font size).

Published April 26, 2015
Categorized as HTML and CSS
Short URL: https://snook.ca/s/1049

Conversation

22 Comments · RSS feed
verpixelt said on April 27, 2015

Thanks for this, Jonathan. I totally like the idea about aligning the icon based on the x-height of the given font. One question though. What do you mean with"... more of a moot point in the future."? Do you just refer to your project? I just don't get the "in the future" part ;) Why not using it now?

Jonathan Snook said on April 27, 2015

@Verpixelt: Because not everybody has high density displays right now, therefore leading to blurring. If, in the future, everybody has 2x or 3x displays, then icons can be rendered to look nice regardless.

Sergei Vasiliev said on April 30, 2015

I usually use margin-top: -.2em to achieve this effect. This can lead to problems?

Sahil Bajaj said on May 01, 2015

Since the icon has its display set to inline-block, we can also use `margin-top: -0.1em` to achieve the same effect, right?

Incidentally, does using transform have any impact on performance?

Jonathan Snook said on May 01, 2015

margin-top can work, too. Transform can create a new positioning context which *may* be problematic.

Mercs said on May 02, 2015

Why not use display: flex; and align-items: center; ?

Cory Fitzpatrick said on May 02, 2015

Why not just use display:table; with display table-cell; and vertical-align:middle; ?

Here is a code pen example of what I am talking about.
http://codepen.io/anon/pen/Wvvaed

Håkan Save Hansson said on May 02, 2015

I usally use line-height:0 or sometimes 1px. It makes it easier to control the vertically centering, I think.

Patryk said on May 02, 2015

I use always following solution when I know icon size.

Pros:
1. Less DOM elements
2. You can modify line-height, font-family, font-size but icon is always perfectly aligned.

div{
position: relative;
padding-left: 20px;
}

div:after{
content: "";
position:absolute;
left: 2px; /*2px gaps between div left edge and icon, icon and text*/
top: 50%;
width: 16px;
height: 16px;
margin-top: -8px;
}

Kyrodes said on May 02, 2015

> The ex unit can be useful because it’s based on the x-height—the height of the letter X.

Just to be more exact. ex unit is the height of the lowercase "x".

Bart Veneman said on May 02, 2015

I usually use verical-align: -1.em: possible because of display: inline-block. It's a little more cross browser than transforms and, as far as I know, doesn't create a new context.

Lu Nelson said on May 03, 2015

I believe the offset could also be generalized as

$height: 1em; // any height, any unit
.icon {
    display: inline-block;
    height: $height;
    vertical-align: calc((1em - 1ex)/4 - ($height - 1ex)/2);
}
Lu Nelson said on May 03, 2015

whoops, bad formatting. this:

// Sass
$height: 1em; // any height, any unit
.icon {
    display: inline-block;
    height: $height;
    vertical-align: calc((1em - 1ex)/4 - ($height - 1ex)/2);
}
Bob Rockefeller said on May 03, 2015

When using:


.icon {
  width: 16px;
  height: 16px;
  display: inline-block;
  background-color: salmon;
  vertical-align: middle;
  transform: translateY(-.1em);
}

How does the -0.1em hold up over a range of possible text sizes?

Stef said on May 04, 2015

I usually give height and line-height the same value to achieve the same result.

div {height: 16px;line-height: 16px;}

Stephen said on May 04, 2015

Percentages can also be used.
vertical-align: 50%

Ferdy said on May 04, 2015

+1 for using line-height, always fixes it for me.

David Gilbertson said on May 04, 2015

Huh, I did not know that vertical-align took a percentage (of line-height) or a length (both relative to the parent baseline).

Nice.

Morgan Feeney said on May 05, 2015

The problem surrounds your choice of markup. Unless you are deliberately using the bare minimum; creating what seems (to me) to be an edge/use case. Personally I would either use a pseudo element and wrap a span around the text. Then set both to inline-block and apply vertical align[whatever] to both the span and the pseudo element. Why not do that instead? Or does this have to work only the specific situation you have demonstrated?

Teelah said on May 05, 2015

Life saver, works for me with fontello.

Jonathan Snook said on May 06, 2015

@Morgan Feeney: That works if the text next to the icon doesn't need to wrap. While minor, it also means creating an extra class along with the extra HTML. Byte for byte, I don't think you end up ahead.

tucq said on May 10, 2015

I often use tricks like top, bottom or line-height to fix this stuff. Now I see the heaven. Thanks alot J

Sorry, comments are closed for this post. If you have any further questions or comments, feel free to send them to me directly.