Replacing the -9999px hack (new image replacement)

IN THE BEGINNING was FIR, AKA Fahrner Image Replacement (note that one of the following links returns a 404):

The Daily Report’s 2003 redesign uses (and our book explained) an image replacement technique intended to combine the benefits of accessibility with the power of graphic design. We christened this method Fahrner Image Replacement (FIR) in honor of Todd Fahrner, who first suggested it to us. Douglas Bowman’s tutorial popularized the technique, which was first developed by C. Z. Robertson in 1999. (Robertson, Fahrner, and Bowman each developed the idea independently.)

Preceding web type by a decade, Fahrner gave us the ability to use fonts other than Verdana and Georgia on web pages (i.e. to set type in Photoshop and export it as an image—an ancient web design practice) but to conjure these images of text via semantic HTML markup (the words the text pictured, set in appropriately structural HTML elements).

Then came Phark’s Accessible Image Replacement, which improved on the Fahrner method by avoiding edge case failures and accessibility problems inherent in FIR. The principal aspect of the Phark method—the part of it all of us remember and use to this day—was this little piece of code:

-9999px

So popular was this method, we made a tee shirt out of it, and it sold, baby, it sold.

But despite its enduring popularity, Phark has drawbacks of its own: chiefly, a performance hit caused by the need to draw a giant 9999px box offscreen. (Yes, the browser really does this.)

My friend Scott Kellum, design director at Treesaver, has now sent me this refactored code for hiding text, which I hereby christen the Kellum Method:

.hide-text {
text-indent: 100%;
white-space: nowrap;
overflow: hidden;
}

  • Really long strings of text will never flow into the container because they always flow away from the container.
  • Performance is dramatically improved because a 9999px box is not drawn. Noticeably so in animations on the iPad 1.

_________________

147 thoughts on “Replacing the -9999px hack (new image replacement)”

  1. Seems like this would be a good alternative method for skip links too. In a little test case, it seems to meet req’s in the same manner.

  2. Thanks for the tip!
    I just implemented this on an element “.site-name a”, which I was replacing with a background image. However I also had to add “cursor: pointer;” for the correct cursor to come up.

  3. I always used the ems as well instead of px for this value, but I’m not sure why I never thought to use 100%—that makes way more sense. Guess I’ll be changing my ways! *bookmarks the page* :-)

  4. I honestly didn’t know that the browser still drew a 9999px wide box. That’s an awesome tip, thanks for sharing!

    Also, anyone still selling those -9999px tees?

  5. I always thought there was a better way to handle this, but never really thought about the enormous box the browser had to draw or the performance hit involved. I’ll definitely start using this method over the other, especially since I was using -9999em instead of pixels quite often, which would create an even MORE ridiculously large box.

  6. Wow, imagine those control buttons on a huuuuuuge listing!!
    Number of list items * 9999px = xxxxxxxxxxxx px!!!

    Great approach. Thanks for sharing.

  7. Was anybody else expecting some kind of new CSS3 fanciness? I believe these rules were supported back when the original hack was made, which I find quite surprising!

    Good stuff, thanks, consider my habit changed.

  8. What if you used overflow:hidden on the body or html for the -9999px trick? Would it still draw a > 9999px box?

  9. Very interesting. We’ve been tweaking our app to wring every bit of performance improvement out of IE7 we can. I wonder how much difference it will make.

    I’m assuming the DOM element still needs to be set to block or inline-block for the overflows and and indents to take proper effect.

  10. Very interesting. I’ve been tweaking our app every bit of performance improvement out of IE8, Chrome, Safari and Firefox. I wonder how much difference it will make if I tune it to all available browsers. Just kidding. it is very interesting tho what you said:-)

  11. @Patrick It’s true they were all in the spec, but the nowrap one didn’t work properly if I remember correctly.

    How’s the accessibility impact of this method? Keyboard nav?

  12. Wow, crazy. Go Scott!

    This is the first I’ve heard of negative text-indent incurring a performance hit. Is there somewhere I could read about that?

  13. Wow, what an elegant solution! I had no idea the -9999px took up so much performance. What tools or plugins do you use to monitor that?

  14. How do you test the performance of this? It’s browser rendering speed…right? @Scott Kellum – I don’t see any difference in your example.

  15. Damn, and there I was thinking I was being extra careful by using -30,000px, not aware that I was being 3x less efficient. Because, y’know, that 10,000px wide screen *might* have come along in our time, too :)

  16. Over at the HTML5 Boilerplate project we’re taking a look at this. So far, we’re unable to reproduce the performance advantage on modern hardware. It may just join the ranks of css gradients and box shadows as things to avoid on old hardware before things like the webkit-box-shadow perf fixes went in. http://wkb.ug/22102

    https://github.com/h5bp/html5-boilerplate/issues/1005 We’re still investigating…

    But to put this into perspective, removing one script tag from your head would have, at a minimum, around  300x more impact than this reflow optimization. But if you’re optimizing reflow on iPad 1 devices, then it’s certainly worth looking into.

  17. Seems like a more elegant solution, but I’m grateful for Paul’s comment above, in which he gives a very “real world” analysis. We’re all often guilty of picking out wallpaper, whilst failing to notice the room is on fire :P

  18. Wow. I didn’t know about the big box and (apparently arguable) performance hit, but I feel better knowing that @beep didn’t either. :) I will definitely be trying this out.

  19. What’s this good for these days? What about downloadable fonts, Google fonts API etc.? There is no sense in trying to refresh outdated techniques.

  20. One of those “why didn’t I think of that!” moments. Around a decade of using the negative text-indent without questioning it… this new tequnique is much nicer.

  21. I really like the thinking behind this. Since it does affect things like the iPad 1 (which I have) I am keen to optimize, however, i’m struggling to get it to work when using it on absolutely positioned elements, but that could be something at my end :D

    Now I just need to go through all my other bits of code and remove the -99999px (yep extra 9… I was future proofing!)

  22. @dusoft asked:

    What’s this good for these days? What about downloadable fonts, Google fonts API etc.? There is no sense in trying to refresh outdated techniques.

    Glad you asked! Obviously, you wouldn’t use it to replace headline text. You can do that with web fonts.

    If you “just had to” use a typeface which had no licensable web font equivalent, you still wouldn’t use this method: there are other (also outdated) methods around like SIFR and Cufon that let you replace text with a font that’s not otherwise available on the web—but those outdated techniques, great in their day, also have downsides: SIFR won’t work in a fair number of important mobile contexts; Cufon, because of its technological implementation, probably violates the license agreement that came with your font. If I “just had to” use a specific font that was only available to me via SIFR or Cufon, I’d almost certainly suck it up and pick a different font.

    So what is this technique good for? Well, you might use it with a logo that is text-based. Most logos are, and to try to reproduce an actual logo using web fonts and tortured CSS would be an interesting design class exercise, but not something you’d actually do on a professional web page. In some cases, that logo can be served as a straight image with alt text inside an appropriate HTML element — such as the type treatment at the top of this site in its current fixed-width form.

    But say you are creating a responsive design, and need to switch out differently sized logo images depending on the context in which they’re viewed. Ah! Then you would use Scott Kellum’s technique and be very, very grateful for it.

  23. Personally this is more confusing that text-index:-999em as it’s harder to remember (yes I know you’ll have a standard CSS file which this would always be included but if you’re working on someone elses code and you wanted to remember that css trick to hide text then text-index is much easier to remember).

    Also this seems like a proper micro-optimisation for a real edge case scenario but it’s presented as the defacto way to do this type of thing from now on which I think isn’t how this should have been presented.

    Just my opinions and I may be wrong but that’s my input.

  24. Fantastic. Thank you so very much for sharing (feels like 10 years ago again!)

    @Paul Irish: Even if the performance improvement is minor (and I believe you), this feels like a cleaner, less-hacky solution to the problem. The performance increase on older devices, iPad 1, etc. is a (responsible) bonus.

  25. Exactly the same issue than the negative text indent technique: nothing is visible for a user with CSS enabled but images disabled. Accessibility, anyone?

  26. In my early experiments using this new technique, it fails in cases where padding has been applied to the element being replaced. Perhaps I shouldn’t be adding padding in this instance anyway, but I thought it was an issue worth highlighting.

  27. Thanks for this! It looks much more meaningful than throwing our text to the infinite and beyond (on the left) 8-).

    Anyway, I had this minor problem with it: I used it to replace an “about me” logo and found some of the link text showing (part of the “A” from “About me”, as you can see in the following capture).


    (part of an “A” is showing just before the first line on the upper right hand).

    I understand it has to do with me applying some padding to the tag trying to achieve my intended result. Sorry if I am a bit of a newbie when dealing with CSS, but I played with it and it seems the “overflow: hidden” property is making text-indent to use width instead of that of ‘s parent element. I used text-indent: 150% to “kick” it a little to the right until I find a perfect solution.

    So, my advice would be to be careful with this replacement if you are applying padding to the element, or use a higher text indent to prevent it.

  28. Would it be worth adding ‘box-sizing: border-box;’ to fix that glitch Paul mentioned above? Still some browser support issues mind…

  29. Thanks for sharing. I’ve been using a similar method of image replacement for a few years, but instead of using the text-indent property, I’ve always used top & right padding on a fixed-size element. I will definitely be playing with the method you shared above to see how it improves things.

    To add another similar concept to your list of potential places to use this sort of image replacement, I find myself using it quite often to create text links to things like Facebook, Twitter, etc., then replacing the text in the links with an appropriate icon or button.

  30. One of the promising things about advanced OpenType features coming to the web is that companies could commission a typeface that automagically becomes their logo when the company name is typed. The file size would be pretty small (you wouldn’t need a full character set), and it’d scale well, regardless of context.

    But! — there aren’t many browsers that support that stuff (yet). Until there’s much wider support, this image-replacement technique looks like the way to go.

  31. I tried to break this method by using absolute positioning and applying padding, but it holds up for me, at least on Webkit. Those that are having issues, what OS/browser are you running?

  32. This is an improvement, but I’ve honestly never found a valid reason to use the -9999px method or any other text hiding technique. Google penalizes sites for hiding text, it is thought of a “untrustworthy”. If the purpose is SEO, why not just add a title attribute to the image?

  33. What I meant to say was, if the purpose is “accessibility”, why not use the title attribute, which will still be read by screen readers.

  34. Honest question (don’t have an iPad near me to test), but wouldn’t this be accomplished by throwing the text into a second span and add this to the css?

    #new > span > span { opacity:0 }

    Like I said, no time to test… just thinking out loud.

  35. This is great, and i’ll use it for a little while, but still has accessibility issues (text doesn’t display when images are turned off).

    When I have more time I’m going to finish getting the same results with the :before and :after pseudo elements (inspired by Chris Coyier). I’ve used a pseudo element with a background image to cover the text (and styled the text not to wrap). Works in webkit. Need to debug elsewhere. If you get it dancing everywhere, share the love.

  36. [novice question alert]

    With respect to Jeffrey’s example and these techniques in general, here’s something I’ve always wondered about but never bothered to ask:

    What’s the drawback of just including the text of the logotype as an or tag right before the logo’s and then hide that text element with CSS, then if you need to, make the responsive with media queries or javascript?

    Example:

    http://jsfiddle.net/rT2fN/9/

    [Note: I dunno if for this example another one of the H5BP classes like `.visuallyHidden` or `.invisible` would be more appropriate.]

  37. Would be interesting to understand both the SEO and accessibility impacts of this approach.

  38. Sweet! This fixes an issue I’ve been having with the Phark method causing flashing in Safari.

  39. I’ve been doing something similar but less object oriented. It requires you know the height and width of the box you’re replacing but with most image replacement you do.

    I find I use image replacement the most these days when inserting images from sprites. Text on the page serves the function of alt text. Using it now for a series of icons:

    10 Votes

    21 Check-ins

    .meta-icon{
    height:18px;
    width:0px;
    padding-left:18px;
    overflow:hidden;
    background: url(icon-sprite.png) no-repeat;
    }

    .meta-checks .meta-icon{background-position:0px -18px;}

  40. While I think this is certainly and interesting approach, I have some concerns with the accessibility. In some, if not all, cases when overflow: hidden; hides the content of the element this is applied to from screen readers. In most cases where I use image replacement, I still need the text to be accessible (e.g. call to action buttons set in Gotham). See Aaron Gustafson’s A List Apart article, http://www.alistapart.com/articles/now-you-see-me/.

    Has anyone tested this with a wide battery of screen readers or other accessibility devices?

  41. @Zeldman it might be good to add

    letter-spacing: -.4em;

    …to the mix, to contract the box-size even further.

  42. @Scott kellum won’t google penalise the use of Jonathan’s suggestion as hidden keyword stuffing!?

  43. It won’t be long before older browsers like IE7 will start to disappear.
    When that happens I’ll start using a modified version of Nicolas Gallagher’s method for applying multiple backgrounds to an element. (http://nicolasgallagher.com/multiple-backgrounds-and-borders-with-css2/)

    [html]

    Company name…

    ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

    [CSS]
    h1.logo {
    position: relative;
    font-size: 34px;
    line-height: 34px;
    width: 85px;
    height: 34px;
    padding: 0;
    z-index: -1; /* The z-index would normally be 1 for the content, but we want it to be behind the image. */
    }
    h1.logo:after {
    content: ”;
    position: absolute;
    background-image: url(‘company_logo.png’);
    background-position: 0 0;
    z-index: 1; /* The z-index would normally be -1 for the additional background, but we want it in front of the image.
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    }​

    Using this, the text is still visible and valid even if the image fails to load.

  44. Does anyone have a citation or more information for the bit about “the browser” “drawing a box” or otherwise incurring a rendering overhead for elements positioned outside the viewport?

    Info on this is a little tricky to come by, but I’m fairly sure that Webkit at least optimises out any off-viewport elements during rendering. I’d be surprised if Gecko doesn’t do likewise.

  45. Thanks for this neat little replacement idea. Although I’m having issues getting this to work in Firefox 10.0.2. Works fine in all other browsers. Any ideas why?

  46. I’ve a big client site where I have 100+ instances of text-indent:-99999px; which I switched over to (+)100% last Friday, without overflow or whitespace property. First review saw not all instances with explicit height and width get pushed to the right extending the window. I’m sure this would of been fixed with the extra overflow and white-space declarations, but switching the (+)100% to (-)100% meant that it worked just as the -99999px rule did, without the extra properties.

    :)

  47. Following up with some research findings: this excellent talk on webkit performance optimisation by Matt Seeley at Netflix:

    http://www.youtube.com/watch?v=xuMWhto62Eo

    At 35:20, Matt mentions discovering that the render layer for something that’s on screen with a massively negative text-indent value is calculated to be that text-indented size.

  48. Does anyone else feel that setting overflow: hidden is a dangerous thing to do? Any container using that property should be the exception and not the norm, there’s no reason to assume that just because you want to hide the overflowing text, you also want to hide everything else that overflows. This is what the -9999px text indent, default overflow gave you.

  49. I wonder if font-size:0 would be better? I’m guessing it’s a no-no due to screen reader behaviour.

    I suspect {color:transparent} would be better altogether.

  50. Why not just do this?

    .text {
    overflow: hidden;
    width: 100px;
    height: 0px;
    padding-top: 100px;
    background: url(‘image.png’);
    }

  51. This is a very smart solution. Even if you need an extra declaration.
    It seems to work in FF too (I tested it in Chrome, FF, Safari and Opera). Pending on multiple devices.

  52. So I hate to be that person, but… it doesn’t seem to work in IE6. Anyone else seeing the text showing? I’ll try to revisit this when I have time and figure out what’s not working. (Yes, we still support IE6 — we have paying customers whose sole browser is IE6 and we are not in a position to make them upgrade.) (Yes, that sucks.)

  53. Nice!

    I never used the -9999px. I was a visibility:hidden person. I would wrap a span inside my [a] and hide the span, but this is much better. More semantic code, no need for extra tags to hide it.

    Brilliant. Except now I’m ashamed of every site that I have out on the net, because it’s all archaic code.

  54. And what do you guys think of this technique ?

    With an image of 30px high :
    .foo {
    height:0;
    overflow:hidden;
    padding-top:30px;
    }

    Anyway, thank for sharing your snippet :)

  55. Pingback: CSS | Pearltrees
  56. @Joel & @William that is also the method I use. I would love to get feedback on it. I call it the Feux Sprite method because of how closely it resembles moving sprites behind the box model.

  57. I tried this in Chrome on a H5BP site and got a mysteriously off-center image; inspecting revealed extra white space to the right that could not be removed by zeroing margin or padding, nor by shifting the background coordinates over—as soon as the image hit that area, it was cut off; changing overflow had no effect either.

  58. I’ve been doing this for the past couple of years – I had no idea it wasn’t already common place.

  59. As a few people said already, this does not solve the accessibility problem that comes with text-indent. Worse, it may send the wrong message: “this is new and cool, use this from now!”.

    As a leader in the industry, I think you should warn people that even if this is “better” in term of performance, it is still a bad solution.

    Imo, Image Replacement techniques should be evaluated against the problems they solve/address. Fwiw, I wrote something about these challenges a few years back:
    http://tjkdesign.com/articles/tip.asp </shameless plug>

  60. According to Scott Kellum, the difference for him was just 0.007 ms. This is 7 thousandths of a millisecond! Those who think that this tip would save them time should refresh their math skills.

  61. And what do you guys think of this technique ?

    With an image of 30px high :
    .foo {
    height:100px;
    overflow:hidden;
    padding-top:30px;
    }

    Anyway, thank for sharing your snippet :)

  62. Thanks for great solution. I tested it on IE6 and it worked fine. I’ve made an article about this solution to our company’s blog (in Japanese), insisting on your blog and your friend’s name, of course. I hope it’ll be some help for my friends also.

  63. The amout of times I’ve used similar patters and never thought of doing that. Feel a little institutionalised… Need to be a little more inventive from now on!

  64. Is there a similar idea/technique for the accessible hiding of block elements? I currently use position: absolute; left: -999em;

  65. Been looking at some sites that use -9999px for hiding things in FireFox 11’s new 3D viewer, with just the right rotation you can kindof see the hidden elements floating miles off in the distance.

  66. This sounded cool so I started using it. Unfortunately, IE7 (at least, didn’t bother with 8 and 9) doesn’t always play nice with this. Specifically, I was using image replacement in a table — which contains tabular data, don’t worry — and it was quite loopy. Essentially, it made the table cells 2x the width of the cell above it. I’m guessing there are other situations where this doesn’t work quite right as well, so I I will stick with the proven technique. Fun to learn new tricks all the same, so thanks for sharing!

  67. I like this technique!

    Back in 2004, we tried left:-30000px to hide an entire page fragment (not just a bit of text) and found the performance hit to be quite extreme on IE6, but not so much on Firefox. IE6 basically hung.

    We concluded that IE was indeed drawing huge boxes, despite the fact that they were way off screen. Firefox seemed to be entirely unaffected. (We didn’t test in Opera or Safari/KHTML after seeing the IE problem.)

    In conclusion: if you want to ensure that you’re being as efficient as possible in any rendering engine, don’t use huge numbers that may create huge boxes.

    — John

  68. In addition to some older IE browsers still showing the text, I also find that having any HTML within the container can cause issues – especially something like a line break, or having some kind of header tag or paragraphs. If there’s more than one line, the text wraps… back to the beginning. This despite the white-space declaration.

  69. I used to use -9999px for the h1 tag if its a logo. But now you shouldn’t use it at all.

    You don’t want to hide anything from the user. That is bad SEO. Google will penalize you.

  70. #logo{
    width:100px;
    height:100px;
    overflow:hidden;
    }
    #logo a{
    display:block;
    width:100%;
    height:100%;
    font-size:0;
    line-height:0%;
    color: transparent;
    overflow:hidden;
    }

  71. Brian. This is still a very valid technique, because whether or not this is bad SEO the reality is that if the client/designer wants it they get it.

    Having said that I’m doing mostly mobile web apps these days, so I’m lucky that I’m able to use @font-face. – hopefully one day we will look back at this as a historic web curiosity much like the i.e. box model hack.

    Rob

  72. This seems like a great, simple image replacement technique, but I’ve found that in WebKit browsers, the replaced text is revealed and highlighted when a cmd-f search matches the replaced text. I’ve posted a minimal demonstration here: http://jsfiddle.net/rwaldin/pf93S/

    Using Safari or Chrome cmd-f search for “search” or “reveal” to see what I mean. You might need to step through the matches with cmd-g a couple times. Anyone ideas how to prevent this?

Comments are closed.