Making accessible icon buttons

Last week, Pamela Fox tweeted a question to me:


As tends to happen on Twitter, we fruitlessly exchanged 140 character messages trying to get some resolution before I finally offered to write a blog post explaining my view. Along the way, I discovered that I had misunderstood the original question (yay 140 character responses) and needed to do a little research. And so here it is.

Simple icon buttons

In the beginning, there <input type="image">. Many seem to have forgotten this part of HTML. Early on, web developers wanted to use images as submit buttons rather than the plain submit button and <input type="image"> allowed you to create an image that actually works like a button. Further, this type of image actually announces itself as a button in screen readers. Anytime you want the user to click on something and not navigate to another page, you’re looking for a button, and <input type="image"> gives you a nice compact way of doing that while supporting the same attributes as <img>. For example:

<input type="image" src="email.png" width="14" height="14" alt="Email">

In this case, the major screen readers (JAWS, NVDA, VoiceOver) announce “Email button” in all major browsers, reading the alt text and identifying the image as a button. Part of the reason this pattern isn’t used much anymore is due to the ubiquity of CSS sprites. However, it’s still my favorite pattern, and with a little adjusting, works fine with sprites:

    .email-btn {
        width: 14px;
        height: 14px;
        background: url(activities.png) 0 -85px no-repeat;

<input type="image" src="pixel.gif" class="email-btn" alt="Email">

This example uses a single transparent GIF as the src of the image. Doing so allows a background image to be applied and show through via the “email-btn” class. If the thought of having an extra download for an icon button is unpalatable to you, you can also use a data URI representing a single transparent pixel:


In either case, the major screen readers still announce “Email button” when focus is set to the button, which is exactly what you want.


The <button> element has becomes the element of choice for making non-text buttons in HTML. This makes complete sense because you can put whatever HTML you desire inside and it renders as a button. However, the end result is not the same as with the more traditional approach. First, consider a really simple example:

<button><img src="email.png" width="14" height="14" alt="Email"></button>

This button produces some very different results in different browsers and different screen readers:

  • Chrome 25 (Win7/NVDA): “Button”
  • Internet Explorer 9 (Win7/NVDA): “Button”
  • Firefox 19 (Win7/NVDA): “Email graphic button”
  • Chrome 25 (Win7/JAWS): “Email button”
  • Internet Explorer 9 (Win7/JAWS): “Email button”
  • Firefox 19 (Win7/JAWS): “Email button”
  • Chrome 25 (Mac OS X/VoiceOver): “Email button”
  • Safari 6 (Mac OS X/VoiceOver): “Button”
  • Firefox 19 (Mac OS X/VoiceOver): “Email button”
  • Mobile Safari (iOS 6/VoiceOver): “Button”

So basically, using a <button> element introduces a barrier for most screen reader/browser combinations to figure out what the button is doing. It doesn’t matter if the <img> is used more traditionally or with a sprite, you don’t get much information in most places.

You can give screen readers a hint by using the aria-label attribute on the <button> element. Doing so means providing a plain-text label for the button as a whole:

<button aria-label="Email"><img src="email.png" width="14" height="14" alt="Email"></button>

By adding an aria-label, the various screen readers and browsers respond as follows:

  • Chrome 25 (Win7/NVDA): “Email button”
  • Internet Explorer 9 (Win7/NVDA): “Email button”
  • Firefox 19 (Win7/NVDA): “Email graphic button”
  • Chrome 25 (Win7/JAWS): “Email Button”
  • Internet Explorer 9 (Win7/JAWS): “Email Button”
  • Firefox 19 (Win7/JAWS): “Email button”
  • Chrome 25 (Mac OS X/VoiceOver): “Email button”
  • Safari 6 (Mac OS X/VoiceOver): “Email button”
  • Firefox 19 (Mac OS X/VoiceOver): “Email button”
  • Mobile Safari (iOS 6/VoiceOver): “Email button”

So now you actually have a nice response from all of the major browsers and screen readers. You can use this same technique regardless of what you place inside of the <button> element.

Font Awesome

Part of what I missed from my original Twitter discussion with Pamela was that she was using Font Awesome1. For the uninitiated, Font Awesome is an icon font that contains numerous common icons for use in HTML. Instead of using separate image files or a sprite that you have to manage, you can use an icon font and reference the relevant icon by using a class name. The icon is inserted via CSS, so it has no negative accessibility concerns. This example is similar to the one Pamela brought up:

<button><span class="icon-envelope"></span></button>

The question is, how do you add descriptive text to this? One way would be to add an aria-label attribute as in the previous section:

<button aria-label="Email"><span class="icon-envelope"></span></button>

Since that always works for <button> elements, that’s the fastest and easiest way forward. The result in various screen readers:

  • Chrome 25 (Win7/NVDA): “Email button”
  • Internet Explorer 9 (Win7/NVDA): “Email button”
  • Firefox 19 (Win7/NVDA): “Email button”
  • Chrome 25 (Win7/JAWS): “Email Button”
  • Internet Explorer 9 (Win7/JAWS): “Email Button”
  • Firefox 19 (Win7/JAWS): “Email button”
  • Chrome 25 (Mac OS X/VoiceOver): “Email button”
  • Safari 6 (Mac OS X/VoiceOver): “Email button”
  • Firefox 19 (Mac OS X/VoiceOver): “Email button”
  • Mobile Safari (iOS 6/VoiceOver): “Email button”

I prefer this over the second (and often overused option) of hiding text off screen, in which case you would have code similar to this:

<button><span class="icon-envelope"></span><span class="hide-offscreen">Email</span></button>

The idea here is to position text far off in some direction such that it’s not visible to sighted users but is still read out for screen reader users. I’m not a huge fan of hiding text off screen, primarily because it feels very hacky…a bit like a sleight of hand trick. Additionally, each of the major ways of hiding text off screen comes with some sort of caveat. Using a big negative text-indent doesn’t work well with RTL languages, using a height of 0 means VoiceOver won’t announce the contents, and so on. Jonathan Snook put together a fantastic post2 outlining the different approaches and the caveats to each.

The screen readers all end up announcing the same message as when using aria-label.

Do I use hiding text off screen periodically? Yes, but only as a measure of last resort when I’ve exhausted all other possibilities. I would encourage you to do the same.

One final note: don’t use title as your button label. Example:

<button title="Email"><span class="icon-envelope"></span></button>

While it would be ideal if screen readers were able to use this value, the results are very uneven:

  • Chrome 25 (Win7/NVDA): “Email button”
  • Internet Explorer 9 (Win7/NVDA): “Email button”
  • Firefox 19 (Win7/NVDA): “Button email”
  • Chrome 25 (Win7/JAWS): “Email Button”
  • Internet Explorer 9 (Win7/JAWS): “Email Button”
  • Firefox 19 (Win7/JAWS): “Email button”
  • Chrome 25 (Mac OS X/VoiceOver): “Button”
  • Safari 6 (Mac OS X/VoiceOver): “Button”
  • Firefox 19 (Mac OS X/VoiceOver): “Email button”
  • Mobile Safari (iOS 6/VoiceOver): “Email button”

Even though the title attribute is helpful for sighted users as a hint, it doesn’t provide any real consistent benefit as far as screenreaders go.

Update (07-April-2013)

As several commenters mentioned, my recommendation results in an empty <button> element. I wasn’t too concerned about that at first, but as I thought about it more and more, the purist in me got a little bit upset. Yes, in an ideal world, you should be able to remove all JavaScript and CSS and the page still make sense. In this case, you’d be left with a button that has no descriptive text whatsoever. That turned my stomach and I went back to the drawing board to try and find a way to have text inside of the <button> element without hiding it offscreen. After some tinkering, here’s what I came up with:

.btn-label {
    font-size: 0;
    height: 1px;
    overflow: hidden;
    display: block;

<button class="icon-envelope"><span class="btn-label">Email</span></button>

The idea is to have the descriptive text inside of the <button> element while placing the Font Awesome class on the <button> itself. This allows you to modify the inner <span> element separately from the <button>. The <span> is set to be practically invisible by using a font-size of 0, a height of one pixel (to make VoiceOver happy). The rest is used to ensure the <span> never grows any larger.

In all browsers and screenreaders mentioned in this post, the text “Email button” is announced using this pattern. The solution is something that makes the purist side of me very comfortable. You aren’t tempting fate by moving text offscreen yet the <button> element still has text inside of it. You don’t have to use ARIA to provide additional context for screen readers in this case.


I still prefer the old-school <input type="image"> element for creating clickable icons. However, in the case of using an icon font, that really doesn’t work. In that situation, I prefer to use the aria-label attribute to provide additional text for screen readers. Doing so yields the most consistent treatment for buttons across major browsers and screenreaders. I prefer to use some in-line hidden text to ensure the <button> element has actual text inside. As a bonus, you don’t have to worry too much about how position text off screen might affect other parts of the page.

  1. Font Awesome (GitHub)
  2. Hiding content for accessibility by Jonathan Snook (


  1. Ahmed El Gabri

    Regarding the offscreen text & that you mentioned that it doesn't work well with RTL, I worked on too many RTL websites & I overcame this on an RTL page by reversing the indent from -9999px to 9999px.

    html[dir="rtl] .offscreen{
    text-indent: 9999px;

    Not the best solution ever but sometimes it's needed "I know that you find as a hack" but I just posted here someone might find it useful.

  2. Travis

    The aria-label seems like a good compromise. The only potentialities I see are with text-based browsers (does anyone still use those?) and search engines; both of which are extreme edge cases.

    As for the RTL issue with the text indent, I've had good luck resetting the direction and alignments for just those blocks:

    direction: ltr;
    text-align: left;

    It's unfortunate when the "right" ways don't work as well as we wish them too, but it's good to have fallbacks. Thank you very much for putting this article together.

  3. kev

    I'm not a fan of adding empty SPAN for icon, etc.

    Surely we can just add class(es) to the BUTTON directly for styling?

    e.g. class="icon icon-envelope"

  4. Francisc

    Why not use a pseudo-element?

  5. Jakub Narebski

    Wouldn't it be better to use icon font with ligatures, so that the "email" word is rendered as single "icon-envelope" glyph?

  6. Nicholas C. Zakas

    @Kev - nope, that doesn't work. I'm no fan of empty spans, either, but that's the way it's done, partly so you can add visible text if you want to.

  7. Nicholas C. Zakas

    @Francisc - Font Awesome does use pseudo-elements.

  8. Adam


    If the user turned off CSS then wouldn’t this become inaccessible as you would be left with an empty image with no alt text displaying?

  9. Francisc

    Yeah, but that's just as using an in your example because you are creating a element - extra markup.

    What I meant was why not just use the button:after or button:before pseudo elements.
    Adding that Font Awesome class on the button itself would probably yield the desired result.

    As for having empty elements, you could write the relevant meaning for screen-readers as text and apply an image replacement class like the one used in HTML5Boilerplate (

  10. Nicholas C. Zakas

    @Francisc - again, have you actually tried it? I suspect not, because I did, and it didn't work. Trying to argue your position based on a theory of how things might work typically doesn't yield positive results. :)

  11. Nicholas C. Zakas

    @Adam - yes, that's a very good point. I would much rather have text in the button, however, there are issues (as I mentioned). That's part of why I prefer using actual image elements rather than icon fonts and spans. As a purist, needing to use aria-label hurts me deeply, but sometimes you have constraints to work with.

  12. Francisc

    Tried what?
    If you mean adding the Font Awesome class, yeah, it works.

    But that is a particular case that you mentioned, the general more important point is using a pseudo element on a selector instead of creating extra DOM elements that have no semantic meaning.

  13. Nicholas C. Zakas

    @Fransisc - hmmm, interesting. I wonder what I was doing wrong. I spent a bunch of time trying different combinations and couldn't quite get it to work correctly. I'm going to take another stab at it this weekend, as I've also been playing around with some other ideas around icon buttons.

    I agree, I don't like creating elements purely for stylistic reasons, and prefer to avoid doing so whenever possible.

  14. Brian Richwine

    Just curious, what version of JAWS were you using? Also, have you tried any of these techniques with other adaptive technologies, like Dragon NaturallySpeaking? In many of the higher education contexts I work with, there are more and more students using Dragon NaturallySpeaking and trying to get it to work when browsing the Web.

Understanding JavaScript Promises E-book Cover

Demystify JavaScript promises with the e-book that explains not just concepts, but also real-world uses of promises.

Download the Free E-book!

The community edition of Understanding JavaScript Promises is a free download that arrives in minutes.