Creating bulletproof graphic link buttons with CSS

A CSS problem I have been wrestling with lately is how to create a bulletproof shrinkwrapping graphic button. By that I mean an image-based button that will expand and contract to fit the amount of text it contains. It is a very useful technique for CMS-driven sites that allow the client to change the text that is displayed on buttons, as well as for multilingual sites.

A successfull bulletproof image-based button should:

  • Automatically grow horizontally to fit any amount of text
  • Grow horizontally and vertically if text size is increased or if the text wraps to multiple lines
  • Retain its appearance within reasonable limits
  • Be able to have rounded (or other non-square) corners
  • Have no unclickable areas
  • Be readable when images are disabled

Doing this with CSS may sound easy at first, but there are several things to be considered that make it a little tricky. I also ran into several browser related problems before ending up with the solution described in this article.

Before I go on to describe the problems and the solution, I’d like to give you a friendly reminder that this technique should not be used to mindlessly replace real form buttons.

A real form button (normally <input type="submit">) works equally well without CSS and JavaScript. A styled link, however, can only submit a form through the help of some JavaScript. And since You cannot rely on JavaScript being available. Period., you need to take that into account. What I did in the project I came up with this solution for was to use JavaScript to replace the input element with one of these buttons. That way, if JavaScript is not available, the user can click a real button instead.

OK, let’s move on. The problems, as always when it comes to CSS, occur in Internet Explorer. The first problem is one that I’m not actually sure whether it is a bug or just a different implementation of the specification. I mentioned the problem in Transparent custom corners and borders, version 2, and it consists of IE having problems figuring out the width of block level elements inside floated elements with no width assigned.

When a floated element has no specified width, the browser should adjust the element’s width to fit its content – shrinkwrapping. In the case of a floated link acting as a button, that would come in handy as it will make the link automatically adjust its width depending on the amount of text it contains. Sadly, Internet Explorer will not properly shrinkwrap the following combination of HTML and CSS I use for the Transparent custom corners and borders technique.

  1. <div class="cb">
  2. <div class="bt"><div></div></div>
  3. <div class="i1"><div class="i2"><a href="#" class="i3">A box</a></div></div>
  4. <div class="bb"><div></div></div>
  5. </div>

When div.cb is floated without a specified width, it will shrinkwrap to fit its content. The width depends on the content of div.i1, and that part works fine. The problem is with the div.bt and div.bb elements, which don’t expand to the same width as div.i1. That kind of makes sense, since they are empty siblings of div.i1. A workaround is to give div.cb a width, but I really needed shrinkwrapping buttons, so I had to to come up with something else.

I ran into the second problem while exploring other solutions and experimenting with absolutely positioning the corners. That actually worked, except for a one pixel gap that showed in IE at certain sizes, probably due to a rounding error.

What I ended up with is a link that contains four nested span elements. Here’s the markup for a button (nicely indented here for readability):

  1. <a class="button" href="#">
  2. <span>
  3. <span>
  4. <span>
  5. <span>Shrinkwrap me!</span>
  6. </span>
  7. </span>
  8. </span>
  9. </a>

Yes, I know. That is a lot of span elements. It isn’t pretty, but I can’t think of a better way to achieve this effect until more browsers than Safari support Layering multiple background images. In a production situation I would use a script similar to the one in Transparent custom corners and borders, version 2 to remove the span elements from the markup. The end result is the same to the browser, but the markup is cleaner for us humans who have to edit it.

And of course we need a bit of CSS to turn that markup into a nice-looking button:

  1. .button { /* Top left corner, top edge */
  2. float:left;
  3. color:#ddd; /* Text colour */
  4. background:#333 url(button.gif) no-repeat; /* Fallback bg colour for images off */
  5. font:1.2em/1.0 Georgia,serif;
  6. text-decoration:none;
  7. }
  8. .button * {display:block;}
  9. .button span { /* Top right corner */
  10. padding:6px 0 0;
  11. background:url(corners.gif) no-repeat right top;
  12. }
  13. .button span span { /* Bottom left corner, left and bottom edges */
  14. padding:0 0 0 6px;
  15. background:url(button.gif) no-repeat left bottom;
  16. }
  17. .button span span span { /* Bottom right corner */
  18. padding:0 0 6px;
  19. background:url(corners.gif) no-repeat right bottom;
  20. }
  21. .button span span span span { /* Right edge */
  22. padding:3px 12px 3px 6px; /* Extra padding (3px vertical, 6px horizontal) added to give the text some breathing room */
  23. background:url(button.gif) no-repeat right center;
  24. }
  25. .button:hover,
  26. .button:focus,
  27. .button:active { /* Help keyboard users */
  28. outline:2px solid #ff0; /* Not supported by IE/Win :-( */
  29. color:#fff;
  30. }

You’ll need to adjust the paddings to fit your image if it has wider or narrower corners and edges.

Oh, I made a demo page that contains a few buttons.

Ideally, this kind of button would be accomplished by using a single background image, so that was my initial goal. I did not manage to reach that goal, however, but had to resort to using two images. The two images are button.gif and corners.gif. If you take a look at them, you will notice that they are much larger than any button should become since they define the maximum size of the button. If you need even larger buttons, just increase the size of the images.

Note that corners.gif is transparent except for in the corners. That way it can be used to put each corner in place without covering any of the other corners.

And that’s it. A bulletproof, resizable, shrinkwrapping graphic button.

There are a few caveats though:

  • The corners can’t be transparent
  • It may be tricky to give the button a non-flat background colour
  • It only expands as far as the size of your image

To me, those are completely acceptable drawbacks in most situations.

So, does anyone see how this could be further improved?

Update (2007-05-23): The class attributes on the span elements are gone. I was sure I had a reason for using classes instead of descendant selectors at the time, but I can’t see the need for them now. So no more classitis, and a little leaner markup.

Posted on May 22, 2007 in CSS

Comments

  1. Nicely done! Like you said, it’s a lot of markup, but it’s probably unavoidable. Oh, for the CSS3 multiple-backgrounds per element functionality. :)

  2. May 22, 2007 by Nathan Rutman

    I thought the guys over at Particletree did a nice write-up on styling the button element.

  3. My favorite articles have demos, kinda like my favorite menus having pictures.

    Thx for the sweet setup. I’ve been playing CTRL+scroll on everything (of mine) lately, it’s a real eye-opener…

  4. @Jeff- I think I have wished for the CSS3 multi-background functionality at least once a day for as long as I can remember.

    Nicely done as always, Roger.

    “So, does anyone see how this could be further improved?”

    The only thing I can think of would be to perhaps set this up via JavaScript- have it write all the extra spans and whatnot based on a class. (This would also be a nice plus for CMS based output.) People without JavaScript enabled would simply see a plain button.

  5. Nice work! Man I can’t wait until we can get multiple backgrounds. Yes, it is a lot of markup, but there really isn’t a better way to achieve the desired effect.

  6. Jason: I think that’s what Roger meant by “In a production situation I would use a script similar to the one in…” above. But yeah, you’re right.

  7. When I saw the headline including “Css” and “button”, I got terribly excited. Where I work, we have long used input type=”image”, but we are steadily wanting to move towards input type=”button”, for the benefits you list above (translations, text resizing).

    I was disappointed to see that your solution doesn’t actually use the HTML button element, but rather styles something semantically void to look like a button. It seems to me there are accessibility issues associated with this (i.e. the user isn’t able to activate the ‘button’ by pressing enter).

    Do you have any thoughts on styling actual input elements?

  8. @Nathan : there are (unfortunately) a couple of issues with the button element and IE… this article is a bit deprecated to me.

  9. Do you really need class names for each span? How about just using contextual selectors in the CSS?

  10. @Andrea; this article is not about elements, but about hyperlinks.

    @David Hellsing; that would indeed be cleaner, but I think you’ll have a hard time getting it right in IE6 that way, since it doesn’t understand the more advanced selectors (like the adjacent sibling selector, for example).

    Nicely done, Roger! As you admit yourself; the markup is a bit gruesome, but it achieves all the goals you listed and it’s probably the only way.

  11. Darn, that should be “this article is not about INPUT elements, but about hyperlinks.”

  12. Here is how I did it a couple months ago. No rounded corners, but completely scalable with a gradient background image that swaps on roll-over.

    http://herbergeronline.asu.edu/services/index.php (Hybrid and Online Buttons).

    @Andrea: Check out how stylegala.com does there input buttons. Not sure if it is screenreader friendly as they use display:none; to hide the text “submit” for non-css enabled browsers, but it’s the best I’ve seen.

  13. @Harmen: I couldn’t find any adjacent sibling selectors in the example. IE does understand contextual (descendant) selectors like span span, no?

    @Noah: I created the stylegala buttons once, but they are not very dynamic since the whole button is one image with static text (among other things).

  14. Agreed with David Hellsing, there are no reasons to use classes when it’s possible just to use contextual selectors.

  15. Roger,

    You can do it with only button.gif by making the following changes:

    .button .tr {
      ...
      background: url(button.gif) no-repeat top right;
      margin-left: 6px;
    }
    
    .button .bl {
      ...
      position: relative;
      left: -6px;
    }
    .button .br {
      ...
      background: url(button.gif) no-repeat bottom right;
      position: relative;
      left: +6px;
    }
    

    It appears to work in FF2, Safari, IE6, and IE7. Didn’t test the rest.

  16. Oops, I forgot to note the padding on .button .bl would also need to go away. Here’s the complete rule:

    .button .bl {
      background: url(button.gif) no-repeat bottom left;
      position: relative;
      left: -6px;
    }
    
  17. May 23, 2007 by Roger Johansson (Author comment)

    Andrea:

    It seems to me there are accessibility issues associated with this (i.e. the user isn’t able to activate the ‘button’ by pressing enter).

    The “button” is a link, so there are no accessibility issues I can think of, other than the link not actually looking like a plain text link.

    Do you have any thoughts on styling actual input elements?

    Yes. Don’t do it ;-).

    David:

    Do you really need class names for each span? How about just using contextual selectors in the CSS?

    If I remember correctly I ran into issues that forced me to use classes. I don’t remember exactly what the problem was since it was a while ago that I did this. I agree that descendant selectors would be much cleaner (and that’s what I normally would use), so I’ll have a look at it too see if I can remember what the issue was. If I can’t I’ll drop the class attributes :-).

    Luke:

    You can do it with only button.gif by making the following changes:

    Thanks, I’ll try that and see if it holds up.

  18. Roger, don’t you think you’re sacrificing semantics for rounded corners now?

    Buttons are for actions, links are for navigation. It’s a bit like the GET / POST discussion really … and most of the same arguments apply.

    I’ve always found the LinkButton control in asp.net to be an atrocity… no wonder a lot of people get confused, when the worlds biggest software company makes such stupid controls as samples in their most popular tool.

  19. Very interesting article Roger, thanks. What worries me is are we now asking CSS to do the heavy lifting now for situations that with the advent of fully depreciable DOM scripts could be done more semantically by javascript. After all pretty buttons are definately part of the style layer, and to hack the structural markup layer to this extent to achieve them is perhaps losing the seperation of layers that is the goal?

  20. When I create buttons that need to shrink to fit I tend to use inline-block (and -moz-inline-box). That way they can inline with text.

    However, I’ve been blessed with kind designers so far. I’ve only had to do buttons that needed standard corners. I know Firefox can go mental with text formatting within a -moz-inline-box.

    I partially agree with Morgan on the semantic debate, but I think making links look like buttons can be useful for links which aren’t quite navigation, but aren’t “action” links so don’t require a POST request.

    A good example of this is a link to a PDF, or (my most recent real world example) a “subscribe to podcast” link.

  21. May 23, 2007 by Roger Johansson (Author comment)

    Morgan:

    Roger, don’t you think you’re sacrificing semantics for rounded corners now?

    Not really. These span elements are, in the words of Dan Cederholm, like empty calories. It would be nice if they weren’t necessary, of course. But until everybody uses Safari…

    Stuart:

    What worries me is are we now asking CSS to do the heavy lifting now for situations that with the advent of fully depreciable DOM scripts could be done more semantically by javascript.

    How would using JavaScript to style a document be more semantic? You still need to use CSS, and you still need hooks for the CSS. Until browsers catch up, that means you need markup, either in the HTML file or created by JavaScript. As I noted in this article (and as Anton commented) I would consider using a script to create the extra span elements.

  22. The first problem is one that I’m not actually sure whether it is a bug or just a different implementation of the specification.

    If I’m correct the css spec doesn’t specify how to handle floats without a set width. So browser have to determine by them selfs how to handle this.

    I’ve been fiddling with your examples and it seems to me that the .r span isn’t really necessary. If a removed the backgroundstyling of this element nothing changed in FF. The padding on this element could be moved to .br.

    Also changing the backgroundimage of the .br span, from corners.gif to button.gif, seems to have the same end result in FF. This way corners.gif is only needed for the .tr span. So corners.gif only has to be a 6x6 pixel image which save a bit of bandwidth.

    Good luck with all your fine work.

  23. May 23, 2007 by chris

    You mention CMS in this article, any recommendations on a book to learn CMS?

  24. nice but the glow effect doesn’t show up in IE 6

  25. Nice implementation of css styled links, but as others have mentioned, calling them buttons is misleading.

  26. For those looking for an example that uses actual button elements rather than anchors (2-way, not 4-way, sliding door)… Styling the Button Element with Sliding Doors

  27. May 23, 2007 by Roger Johansson (Author comment)

    Gerben:

    If I’m correct the css spec doesn’t specify how to handle floats without a set width.

    That seems to be the case. I could have sworn that I have read in the CSS 2.1 spec that floats with no specified width are supposed to use the width of their content. Hmm.

    Your suggestions for getting rid of one of the span elements may work. I’ll have to take a closer look at it to verify. I’ll wait a couple of days or so until more people have had a chance to post any comments.

    chris:

    You mention CMS in this article, any recommendations on a book to learn CMS?

    CMS = Content Management System. There are many books available, but it depends on which CMS you want to learn.

    Noureddine:

    nice but the glow effect doesn’t show up in IE 6

    Yeah, well that’s just… IE. It doesn’t show up in IE 7 for the record. No big deal. If you need a better visual cue in IE, just change the :hover, :focus and :active states differently.

    brad:

    calling them buttons is misleading

    Maybe, maybe not. I did call them link buttons though.

  28. May 23, 2007 by chris

    yeah, I know..but I just want to learn the basics of it and learn how to implement it into web pages for future use. Any ones you recommend? thx! :)

  29. May 23, 2007 by Kris

    You can’t have transparent corners, you say? What I usually do is using negative margins to have inner elements suck themselves out of their containers; then when you give them an image background that contains transparency, you won’t see the container background behind it.

    It does make your CSS quite a bit trickier though and IE6 throws a fit sometimes (but throwing some hasLayout or position:relative properties at it often cures).

  30. It seems, looking at the mark-up, that you could remove one set of spans. It’s always hard looking at something versus playing around with it, though.

    Just thinking one input with padding and a default upper left corner and background large enough for adequate resizing, then three spans simply to hold tiny corner images designed to blend with the surrounding background and cover the hard corners of the three remaining corners. Something like this:

    input.button {
      color : #fff;
      background : #333 url(upperleft-and-back.gif) no-repeat;
      cursor : pointer; /* I do this a lot */
    }
    
    input.button span {
      background : url(upperright.gif) no-repeat top right;
    }
    
    input.button span span {
      background : url(bottomright.gif) no-repeat bottom right;
    }
    
    input.button span span span {
      background : url(bottomleft.gif) no-repeat bottom left;
    }
    

    Obviously I didn’t test this, and normally I think I’d class the spans so I didn’t have to nest them, but if it works that way it is slightly leaner so that’s good.

    This is based on my CSS Smart Corners (which is also elastic).

  31. Good write up and invariably brings in the “non-semantic div/span/whatever” debate.

    Another useful link for those looking for the technique of flexible containers (be it for links or for containers themselves) with pretty backgrounds (corners, png opacities, whatever) check out Scott Schiller (now with the flickr team at Yahoo!) with his piece “Even More Rounded Corners”: http://www.schillmania.com/content/projects/even-more-rounded-corners/

  32. Man, I’m sorry Roger. I thought you were discussing inputs. The “link” part of it just jumped out at me. I suppose the technique I offer would work just as well I hope.

    As Homer Simpson would say: D’oh!

  33. May 24, 2007 by Roger Johansson (Author comment)

    Mike: Hehe, np. But, without testing it, I am pretty sure that your technique would not work since it applies a background image to an input element, which is not consistently supported by browsers. Besides, how would you put those span elements inside the input element? D’oh! ;-D

  34. May 24, 2007 by Niklas

    I tried this out and saw that it uses the first 6 pixles of button.gif and then the rest from the bottom and up. How can i change this so that i get the last 6 pixels at the bottom and the rest of the button uses the image from the top. Would be great!

  35. I tried to replace the huge button.gif with only a 1px-width rapport and changed the css:

    .button:link,
    .button:visited {
        float:left;
        color:#ddd;
        background:#333 url(css/images/buttonRap.gif) repeat-x;
        font:1.2em/1.0 Georgia,serif;
        text-decoration:none;
    }
    .button * {display:block;}
    .button span { /* Top left corner */
        background:url(css/images/corners.gif) no-repeat left top;
    }
    .button span span { /* Top right corner */
        background:url(css/images/corners.gif) no-repeat right top;
    }
    .button span span span { /* Bottom right corner */
        background:url(css/images/corners.gif) no-repeat right bottom;
    }
    .button span span span span { /* Bottom left corner */
        padding:5px 10px;
        background:url(css/images/corners.gif) no-repeat left bottom;
    }

    SO the loading time reduces, because of the 1px rapport instead of the huge button.gif.

    I tested it in FF2.0, IE 7, IE 6

    greetz and thanks for your inspirating articles

  36. Besides, how would you put those span elements inside the input element?

    Haha, good point Roger. Note to self: Think first, old man.

  37. May 24, 2007 by Orhan

    Why do you use a “a”-element? “a” is for linking to another document so actually a “button”-element would be more semantic.

    Many people use “a” and just set the “href” to # but isn’t it semantically wrong? Just a thing that I have been thinking about recently…

  38. May 24, 2007 by Roger Johansson (Author comment)

    Orhan:

    Why do you use a “a”-element? “a” is for linking to another document so actually a “button”-element would be more semantic.

    Well, in some cases all you want is a link that looks like a button.

  39. May 24, 2007 by Orhan

    But in other cases people tend to create links for things that actually is a button. I have been doing this myself but now it just seems wrong to make a link that looks like a button.

    Its not a big deal but just a thought. Sometimes you just want a button and “input” isnt for that. “a” is more correct but a “button” seems like the right solution.

  40. I can’t help thinking of this when I look at your css. :)

  41. May 24, 2007 by Roger Johansson (Author comment)

    Orhan: Yes, depending on the situation you may want to use a button element instead.

    Phil: LOL :-D.

  42. Interesting article and all the extra markup would be solved by support of multiple background images.

  43. Very cool, Roger!!!!

    Thanks for sharing!

  44. I read everything with great interest, article and comments:)

    For me, though, Roger, four SPANs we do not actually NEED in the html source look a bit like four TR TD TR TD which have empty GIFs inside to hold them - that is, un-needed.

    Yes, the example you provide is semantic, it’s working with JS disabled or enabled, which is nice. Still, to have these rounded corners, we add a lot of stuff which doesn’t belong to the page, I believe…

    A while ago I remember doing somehing a bit different:

    http://www.cantarco.de/en/order/#cdform

    (There’s a button at the bottom of the page, under the CD order form.)

    The button doesn’t have any rounded corners.

    But, on the other hand, it looks in the source like:

    input type=”submit” id=”but” name=”but” class=”submitbutton” value=”Submit”

    …which is much more semantic to me than 4 empty spans (or calories, as you call them;-)

    Plus:

    1. the button resizes nicely on bumping up text size in Firefox, and text resizes, too,
    2. it has a :hover effect, which is good, too (:hover doesn’t work in IE6, though, but I can live with that;-),
    3. button doesn’t need any JS to look like a button, just CSS,
    4. if CSS is disabled, button looks like a button:)

    Only drawback of the method I’ve used is that button DOESN’T HAVE rounded cornders… Well, I can live with that, too, as far as I do not need any extra JS/ and any ‘empty calories’ to make it work:)

    Yes, I know — rounded corners look nice.

    Rounded corners are better than straight lines.

    But on the other hand, if it comes at a certain price (like extra html elements, extra JS, etc.), then, IMHO, we do not always need these nice corners… One day, a new CSS standard will appear, new versions of browsers will start to support it. Till that day, we can make things a tiny bit simpler, but not styling everything with rounded shapes, if it requires too much code (and some code with no meaning, just for the styling trick…)

    Cheers:)

  45. Definitely brings up the issue of extra divs/classes. I design/build CMS sites everyday, I know it can get a bit convoluted, but man, it makes for solid layouts AND flexible layout/design elements. Ironically, I have found that naming spans for “wraparound” styles is less confusing when it is used for part of the layout.

    Do 3-4 extra spans per element really hurt seo/accessibility? Is the end result worth the extra effort? If there is a price to pay? what exactly is it? I’d like to know.

    There are more performance hitting areas like implementation errors and page loads to worry about than a few extra spans. (Which I really don’t see as too hard hitting). It would be grand day when all browsers are on the same page, multiple backgrounds would make everything so much easier. But in the present day, it really is about how far you can push things within today’s constraints.

  46. June 1, 2007 by Jonas

    Aaaand if you want inspiration for your custum buttons I recommend a trip to: http://www.dragnet.se/webbdesign/button_collection.html

  47. I hate using so many spans or divs to make something work. But when it works it becomes a little harder to deny that this is a usable, accessible and crossbrowser solution. Nasty code, beautiful solution.

  48. July 1, 2007 by Ryan

    Nicely done, really!

    And forget the “button v. link” argument because sometimes you really do want links to look like buttons… and sometimes you even get paid to make links look like buttons as well (so lay off the guy already).

    I would also say that the embedded span tags is not as much a limitation of your solution as it is a imitation of the current CSS standards. And anyone who codes pages for public use has to code to the masses. (Don’t f/blame Roger.. blame the W3C)

    There are hundreds of web menus out there that use variations of this technique. Many of them fall short because they do not scale well when a user increases font size (which is more common than most designers think). Yours scales nicely.

    My only question is in your implementation is one of choice. You used a “max graphic” approach with five levels or elements. Couldn’t you do it with nine sectors and nine tiny background images for both the rendered layout and the hover layout (think of a 3x3 grid). This would remove the “It only expands as far as the size of your image” constraint. Did you have a conscious reason or was it simply how your particular solution evolved?

  49. July 1, 2007 by Roger Johansson (Author comment)

    Ryan:

    Thanks :-).

    I would also say that the embedded span tags is not as much a limitation of your solution as it is a imitation of the current CSS standards.

    Actually it is a limitation of most browsers not implementing CSS fully. Safari has support for multiple background images on the same element, but as far as I know no other browser does.

    Did you have a conscious reason or was it simply how your particular solution evolved?

    Yes, I deliberately wanted to use as few images as possible to cut down on server requests and make the graphics easier to maintain. As some people have commented, it might even be possible to get by on a single image.

  50. Here’s my solution that was tested and works in FF(winXP + mac), IE(win), Safari. I haven’t tested in other environments.

    I’m using 3 images (left.gif, center.gif and right.gif).

    CSS:

    .btn-a{display:block; margin:5px 0; overflow:auto;}
    .btn-lt{float:left; display:block; background: url(../images/left.gif) no-repeat; width:2px; height:16px;}
    .btn-cr{float:left; display:block; background: url(../images/center.gif) repeat-x; text-align:center; padding:0 5px; height:16px;}
    .btn-rt{float:left; display:block; background: url(../images/right.gif) no-repeat; width:2px; height:16px;}
    .btn-a:hover{text-decoration:underline !important;}
    

    HTML:

    <a href="#" class="btn-a">
    <span class="btn-lt"></span>
    <span class="btn-cr">Search</span>
    <span class="btn-rt"></span>    
    </a>
    

Comments are disabled for this post (read why), but if you have spotted an error or have additional info that you think should be in this post, feel free to contact me.