Developer

Designing 3D buttons with pure CSS

CSS buttons are much more efficient than image-based buttons because they're entirely text-based. Here are two techniques for creating a beveled-edge by styling the borders of a CSS button.

CSS buttons are much more efficient than image-based buttons because they're entirely text-based. A simple unordered list is all you need in the XHTML markup—CSS styles take care of the rest. Furthermore, you don't need any JavaScript to swap images for a rollover effect because CSS pseudoclasses let you build separate styles for each of the states (link, visited, hover, active) of a hyperlink.

The only problem with pure CSS buttons is that they tend to look rather flat with a solid color background and a simple border. One solution is to use a hybrid technique that incorporates a background image behind the CSS-styled text button to give it a 3D effect. However, reader e-mail prompted me to look for a way to create a 3D button effect with pure CSS—no images required. I found not one, but two, techniques for creating a beveled-edge look by styling the borders of a CSS button.

Creating a beveled-edge effect

To give a button a 3D beveled-edge effect, you need to simulate a light source creating highlights and shadows on the edges of a raised button. If the light source is above and slightly to the left of the button, then the top and left sides of the button will be lighter than the front face, and the bottom and right sides will be darker than the button face.

So, the secret of the three-dimensional effect is to use CSS borders to simulate the sides of the button and to give each border a slightly different color, depending on whether it represents a highlighted side or a shadowed side. To look realistic, the borders should have mitered corners, and CSS borders meet that requirement nicely.

Technique 1: Using inset/outset borders

As it turns out, there is a CSS border property that is capable of producing a reasonable facsimile of a beveled edge effect automatically. You don't need to do anything more than specify inset or outset as the border-style attribute for your button styles. The browser handles the details of rendering the element borders in slightly different shades of the background color to achieve the desired effect. The outset attribute simulates the shaded edges of a raised button and the inset attribute simulates a depressed button by reversing the shading.

Figure A shows the inset/outset border effect at work. This example is produced with some very simple code. The XHTML markup is nothing more than an unordered list containing the button labels and links.

<body>
<div id="buttonA">
    <ul>
        <li><a href="link1.html">Button 1</a></li>
        <li><a href="link2.html">Button 2</a></li>
        <li><a href="link3.html">Button 3</a></li>
    </ul>
</div>
</body>

The CSS styles that make this technique work are very similar to the styles for a set of plain, flat CSS buttons. The only additions are the border-style: outset and border-style: inset rules combined with a border-width setting that's thick enough to make the effect visible.

body {
    margin: 0px;
    padding: 0px;
}
div#buttonA {
    margin-left: 50px;
}
div#buttonA ul {
    margin: 0px;
    padding: 0px;
    font-family: Verdana, Arial, Helvetica, sans-serif;
    font-size: 12px;
    line-height: 30px;
}
div#buttonA li {
    list-style-type: none;
    height: 30px;
    width: 125px;
    margin: 20px;
    text-align:center;
}
div#buttonA li a {
    height: 100%;
    width: 100%;
    display: block;
    text-decoration: none;
    border-width: 6px;
}
div#buttonA li a:link {
    color: #000000;
    font-weight: bold;
    background-color: #CCCCCC;
    border-style: outset;
}
div#buttonA li a:visited {
    color: #000000;
    font-weight: normal;
    background-color: #CCCCCC;
    border-style: outset;
}
div#buttonA li a:hover {
    font-weight: bold;
    color: #FFFFFF;
    background-color: #999999;
    border-style: outset;
}
div#buttonA li a:active {
    font-weight: bold;
    color: #FFFFFF;
    background-color: #666666;
    border-style: inset;
}

I've covered the technique for creating CSS buttons in previous articles, so I'll just hit the high points.

The div#buttonA ul rule sets the general text size and spacing, while the div#buttonA li rule removes the default bullet from the list items (list-style-type: none) and sets the size of the button box. The div#buttonA li a rule makes the entire button clickable (height: 100%; width: 100%; display: block;), and it's also a convenient place to set the border thickness (border-width: 6px).

The rest of the styles control the appearance changes for the various button states. For each pseudoclass (:link, :visited, :hover, :active), there is a color, font-weight, background-color, and border-style rule. All the styles use the border-style: outset rule except for the div#buttonA li a:active rule, which includes border-style: inset instead. This gives all the button states a raised appearance—except when the button is clicked, in which case, it looks depressed.

There are significant differences in the way the various browsers render the inset and outset borders. Internet Explorer creates a more subtle effect, with a highlight along the inner edge of each border and shading toward the outer edges. Netscape, on the other hand, renders each border in a solid color, which produces a sharper, less rounded, look.

Technique 2: Controlling the individual sides


Using the inset/outset border styles is a quick and easy way to simulate a 3D effect. However, it's not your only option. If you don't like the standard effect, or you're bothered by the differences in browser rendering, then you can take control of the border colors to produce the effect you want.

Instead of using the inset/outset attributes of the border-style and letting the browser handle the actual color rendering of each border, you can set the colors for each border individually with your style rules.

Figure B shows the results of using a style sheet that specifies the colors of each button side individually. The markup is the same as in Figure A. Here's the CSS code:

body {
    margin: 0px;
    padding: 0px;
}
div#buttonA {
    margin-left: 50px;
}
div#buttonA ul {
    margin: 0px;
    padding: 0px;
    font-family: Verdana, Arial, Helvetica, sans-serif;
    font-size: 12px;
    line-height: 30px;
}
div#buttonA li {
    list-style-type: none;
    height: 30px;
    width: 125px;
    margin: 10px;
    text-align:center;
}
div#buttonA li a {
    text-decoration: none;
    height: 100%;
    width: 100%;
    display: block;
    background-color: #999999;
    border-style: solid;
    border-bottom-color: #333333;
    border-right-color: #555555;
    border-left-color: #BBBBBB;
    border-top-color: #DDDDDD;
}
div#buttonA li a:link {
    color: #000000;
    font-weight: bold;
    background-color: #999999;
    border-style: solid;
    border-bottom-color: #333333;
    border-right-color: #555555;
    border-left-color: #BBBBBB;
    border-top-color: #DDDDDD;
}
div#buttonA li a:visited {
    color: #000000;
    font-weight: normal;
    background-color: #999999;
    border-style: solid;
    border-bottom-color: #333333;
    border-right-color: #555555;
    border-left-color: #BBBBBB;
    border-top-color: #DDDDDD;
}
div#buttonA li a:hover {
    font-weight: bold;
    color: #FFFFFF;
    background-color: #777777;
    border-style: solid;
    border-bottom-color: #333333;
    border-right-color: #555555;
    border-left-color: #BBBBBB;
    border-top-color: #DDDDDD;
}
div#buttonA li a:active {
    font-weight: bold;
    color: #FFFFFF;
    background-color: #666666;
    border-style: solid;
    border-top-color: #333333;
    border-left-color: #555555;
    border-right-color: #BBBBBB;
    border-bottom-color: #DDDDDD;
}

Despite the fact that this block of code is noticeably longer than the CSS code for the previous example, it's not that different. The difference is that we replaced the border-style: outset (or border-style: inset) rules with a border-style: solid rule, followed by rules to set the colors of each border individually (border-top-color: #DDDDDD, etc.).

With this technique, you have complete control over the colors of the sides of your buttons. This means that it's up to you to select the right colors to achieve the exact effect you're looking for—and, you'll need to remember to swap the colors around to the appropriate sides to produce the depressed button look for the :active button state. The main advantages of taking control of these details are that you can set separate side and top highlight colors, and the final result is more consistent across all browsers.

Editor's Picks

Free Newsletters, In your Inbox