Making CSS Rollover Buttons

In this tutorial, we'll show you how to create easy, search-engine-friendly rollovers, using nothing but HTML, CSS and images. No JavaScript or IMG tags in sight!

Rollovers are a nice way to add visual feedback to your website's buttons. As a visitor moves their mouse over a rollover button, it changes to indicate that it's clickable.

In the bad old days of HTML, the only way to create a rollover was to use JavaScript to swap the two button images. While this works, it does rely on JavaScript being enabled in the browser. It also adds a fair bit of code bloat to the page, meaning longer download times, and more coding for you.

Thankfully, these days you can create a nice rollover effect using pure CSS; no JavaScript required! Roll your mouse over this button, which is created using just HTML, CSS and a single image:

So how's it done? Well, it all centres around the :hover pseudo-class in CSS. Using this pseudo-class, you can style a link both in its normal state and in its hover (rollover) state. By making the link a block element and giving it a background image, we can turn the link into a button. We then simply jiggle the background image around to create the rollover effect.

The button image

The trick to making this type of rollover work smoothly is to have both the normal and rollover images stacked in a single GIF image, with the rollover state below the normal state. Here's our button image - it's 107 pixels wide and 46 pixels tall:

The rollover button image

On the one hand, this is slightly inconvenient as it means you have to use Photoshop - or a similar image editor - to create a single image containing your normal and rollover images. You can't just grab normal and rollover button images from somewhere and use them as they are. Of course, this is no problem if you're creating your site design in Photoshop anyway.

On the other hand, this approach avoids issues with flickering and preloading, which we'll talk about in more detail at the end of the article. It's also nice, in a way, to have both the normal image and the rollover image packaged within a single image file; it's easier to keep track of your button images, and you only have half as many image files to worry about!

The markup

The HTML for our button is wonderfully simple. In fact it's just a link with an id, and a span element wrapped around the link text:


<a id="emailUs" href="#" title="Email Us"><span>Email Us</span></a>

We give the link an ID - in this case, "emailUs" - which allows us to style the link via our CSS. We also place the actual link text inside a span element. This means that we can hide the link text with our CSS and display the image instead, yet the link still looks like a regular text link to browsers not using the style sheet - such as a search engine spider or a text-only browser, for example.

The CSS

To turn our regular text link into a rollover button, we apply the following CSS:


#emailUs
{
  display: block;
  width: 107px;
  height: 23px;
  background: url("emailUs.gif") no-repeat 0 0;

}

#emailUs:hover
{ 
  background-position: 0 -23px;
}

#emailUs span
{
  position: absolute;
  top: -999em;
}

First we change our emailUs link from an inline element to a block element with display:block. This allows us to give the link a width and height, as well as set our button image as a background. We set the width of the link element to the width of our GIF image, but we set its height to half the height of the GIF; that is, the height of one of the button images. This means that just the top, normal button image appears within the link by default. The bottom, rollover button image is cut off, and therefore remains hidden.

Then we select the link's :hover pseudo-class to style our rollover state. This is simply a matter of shifting our background GIF up by 23 pixels, or half the GIF's height. This hides the normal button image above the top of the link element, and reveals the rollover button image within the link; you can think of it as sliding the GIF upwards within the "window" of the link. When the visitor moves their mouse away from the link again, the button GIF slides back down 23 pixels, returning to its default position, with the normal button image revealed within the link.

Finally, all we need to do is set position: absolute and top: -999em on the span element inside the link. This moves the link text way above the top edge of the browser window, effectively hiding the link text for browsers that support CSS and images.

And that's all there is to it!

Why not just hide the span element using display: none? The main reason is that we want screen readers to read out the link text. Most screen readers won't announce text that is hidden with display: none.

Creating more than one button

If you want to create many rollover buttons in the page - for example, as part of a menu - copy and paste the HTML and CSS, giving each button a unique id in both the HTML and the CSS and, of course, changing the background image for each button in the CSS.

Alternatively, you could style the link text to be in the centre of your button image, rather than being hidden; you'd then only need one (blank) button image for all the menu options. The tradeoff with this approach is that you lose some control over the look of your button text, and the buttons don't generally look as nice.

The rationale

You may be wondering why we've taken the approach of having both the normal and rollover images within the one GIF image. In fact you could easily rework the CSS to include two separate images, as follows:


  #emailUs
  {
    display: block;
    width: 107px;
    height: 23px;
    background: url("emailUsNormal.gif") no-repeat 0 0;

  }

  #emailUs:hover
  { 
    background: url("emailUsRollover.gif") no-repeat 0 0;
  }

What you'll find, though, is that the button "flickers" the first time you move the mouse over it, as the browser fetches emailUsRollover.gif from the server. In other words, most browsers don't preload the background image of the :hover state. By combining both our normal and rollover states in a single GIF, we're forcing the browser to "preload" the rollover state image as it fetches the normal state image.

An alternative approach might be to use a JavaScript preloader, or a hidden img element in the page, to preload emailUsRollover.gif, but then we're kind of losing the point of having a non-JavaScript, pure-CSS rollover!

A nicer alternative is WebCredible's Trifecta button. This uses an inline img element in the markup for the normal state, and a background image in the CSS for the rollover state. To create the rollover effect, it toggles the visibility of the inline image. This means that both images are preloaded with the page, avoiding any flickering effects. It also uses markup to display the button text - a technique that we touched on earlier - meaning that you only need one button image pair for a whole menu.

The drawbacks with the Trifecta button are, firstly, that it requires an extra div wrapped around your link, and secondly, that you end up with an inline image in your markup. Having an inline image means more markup, and also means that you're fairly committed to having image buttons on your site; it's harder to replace your image rollovers with pure CSS versions, or with simple text links, later. However, the Trifecta is a nice solution if you like the idea of separate rollover images.

There's one gotcha with the single-GIF approach, and that's when using our old friend, Internet Explorer 6. If you have this browser set to always fetch files from the server (Tools > Internet Options > Temporary Internet Files > Settings > Check for newer versions of stored pages: Every visit to the page), you'll get horrid flickering each time you move the mouse over the button.

Luckily, it tends to be mainly Web developers that have this setting enabled; most visitors leave IE6 at its default setting of "Automatically".

Microsoft fixed this problem in IE7. Also note that the Trifecta button doesn't suffer from the problem in IE6.

Update 23 Feb 2011: Changed the code to hide the link text using position: absolute and top: -999em instead of using display: none.

Follow Elated

Related articles

Responses to this article

20 most recent responses (oldest first):

19-Aug-11 12:23
Is your button image (sprite) exactly 138x106? If it is not, that is the reason the sprite is hugging to the left. I also don't understand why you say the text is centered? Are you using the <span> tag for the text view or is the text embedded into the image sprite that you are using?
19-Aug-11 13:43
Yes, the image res is exactly 138 x 106.

What I mean when I say the text is centered is that the rest of the text contained in the "navbarleft" div (and therefore, all the rest of the contents of that div) is centered. I apologize, I probably should have also attached the css for those other properties. Here is the code for the page in its entirety:



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>New Video Template</title>
<style type="text/css">

#navbarleft {
position: fixed;
left: 0;
width: 15em;
text-align: center;
font-family: Arial,Helvetica,sans-serif;
margin-left: 10em
}

#logo {
float: right;
padding-right: 15px;
}

#title {
font-family: georgia,serif;
text-indent: 15px;
background-color: #FFE6B2;
padding-bottom: 10px;
padding-top: 30px;
border-bottom: 15px solid black;
border-right: 15px solid black;
border-left: 15px solid black;
}

#date {
font-family: georgia,serif;
text-indent: 15px;
padding-top: 1px;
background-color: #80B2E6;
border-bottom: 15px solid black;
border-left: 15px solid black;
}

#maincolumn {
border-left: 15em solid #1975D1;
background-color: #FFFAF0;
font-family: Arial,Helvetica,sans-serif;
}

#programsection {
background-color: #FFFAF0;
border: 2px outset;
border-left: 15px solid black;
}

#presiding {
background-color: #FFCC66;
padding-top: 3px;
border-bottom: 15px solid black;
border-right: 15px solid black;
border-left: 15px solid black;
}

#wrapper {
margin-left: 10em;
margin-right: 10em;
width: 75%;
background-color: #b0e0e6;
}

#orderdvddiv {

}

#orderdvd {
display: block;
width: 138px;
height: 53px;
background: url("../graphic/OrderDvdButtonStacked.gif") no-repeat 0 0;

}

#orderdvd:hover {
background-position: 0 -53px;
}

#orderdvd span {
position: absolute;
top: -999em;
}

p {
font-family: Arial,Helvetica,sans-serif;
text-indent: 15px;
}

body {
margin: 0;
}

h1 {
margin-top: 0;
color: black;
font-size: 2em;
}

h2 {
color: white;
}

h3 {
text-indent: 45px;
}

h4 {
text-indent: 45px;
text-transform: uppercase
}

h5 {
font-family: georgia,serif;
font-size: 1.5em;
color: white;
}

h6 {
font-family: georgia,serif;
font-size: 1.5em;
color: white;
}
</style>

</head>

<body>

<div id="wrapper">

<div id="navbarleft">

<h5>Seminar<br />Number</h5>
<h6>######</h6>
<br />
<br />
<div id="orderdvddiv">
<a id="orderdvd" href="http://www.iclega.org" title="Order DVD"><span>Order DVD</span></a>
</div>
<br />
<br />
order form button
<br />
<br />
<br />
<h6>Self-Study CLE Hours:<br />
6 CLE Hours including 3 Trial Practice Hours</h6>

</div>

<div id="maincolumn">

<div id="logo">
<img src="../graphic/oformlogo.gif" alt="" width="64" height="72" />
</div>

<div id="title">
<h1>Program Title</h1>
</p>
</div>

<div id="date">
<h2>Record Date</h2>
</div>
<div id="presiding">
<h3>Presiding/Co-Sponsor</h3>
<p>Name or Names of Presiding Speakers</p>
<br />
<h3>Speakers</h3>
<p>Name or Names of Speakers and Their Representative Institutions</p>
<br />
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>
<div id="programsection">
<h4>Program Sections Title</h4>
<p>Names of Speakers on This Subject</p>
</div>

</div>

</div>

</body>
</html>


I appreciate your assistance!
19-Aug-11 14:03
OK! Got it! Check out these two modified selectors:



#orderdvddiv {
width:100%;
}

#orderdvd {
display: block;
width: 138px;
height: 53px;
background: url("divback.jpg") no-repeat 0 0;
margin: 0 auto;

}



Basically, you are going to make the #orderdvddiv 100% of the width of it's parent, #navleftbar, and then down in #orderdvd you are going to set it's margin to "0 auto" which will set the top margin to nothing (because we don't care about it) and the right margin to auto, which will automatically get the distance of the block element's right and left position of the parent and then set the pixel amount to the half of that. Hopefully that works for you.
Check it in IE 8 and below because I am not sure if it will work. I am pretty sure it will since we have previously stated to set the element's display as block.

[Edited by cwalkdawg on 19-Aug-11 14:04]
19-Aug-11 14:13
Sweet! Looks like it should work just fine Thanks Dawg! I think I will have to use that code on a number of different elements as I continue to work, so I really appreciate your help! (I'd much rather know how to do it correctly than to have to jackleg it every time...)

Yeah, I work on a Mac, and I"ve kinda been dreading see what's gonna happen when I finally test it on the PC in our office in IE...
19-Aug-11 14:35
I just looked it up and using the display block should work fine on at least IE8.

I work on Linux, Mac, and Windows and it always gets mixed up or messed up somewhere. Just gotta look for the fixes.

Glad I could help.
19-Aug-11 14:44
Are there any applications for Mac that simulate IE environments to test code on that you would recommend?
20-Aug-11 01:01
http://ipinfo.info/netrenderer/
Give this site a look at. I haven't used it myself because I test with an application in Vista, but supposedly you can render the pages online.
20-Aug-11 21:36
Cool, thanks again!
23-Aug-11 22:39
@cwalkdawg: No problem at all, I really appreciate you helping out

@Ladger: Works for me! I guess you fixed it.
24-Sep-11 08:55
Awesome article and I use this everywhere. However, there is one thing I don't get. On Hover we are shifting the image "UP" but the CSS seems to indicate moving it to the "Left". I've seen other (less elegant) CSS that actually does the same thing and actually uses "background-position: left -23px".

Why does this cause the image to move UP instead of to the LEFT?

Thanks for awesome articles.
25-Sep-11 04:40
@ltyre
Background-position specifies vertical and horizontal. It does not specify a direction. Thus the


background-position: left -23px;


declares that the position of the background image will sit to the left of the element and -23px from the top edge of the element.
So if you want to shift an image to the left by say, -23px, you would say



background-position: -23px top;


http://www.w3schools.com/cssref/pr_background-position.asp
20-Dec-11 17:59
Hi Matt,

I see you've been answering questions on this tutorial for quite some time...good on you!

I have an issue. I've followed the code and have the button sitting in my webpage but it will not move to the second position when hovered over.

I've uploaded the temporary page here:

www.nickworley.co.uk/index_split.html

HTML Code:


<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Nick Worley</title>
<link href="CSS/2_Column_Layout.css" rel="stylesheet" type="text/css">
</head>
<body>
<div id="container">
<header id="sidebar">
<img src="Images/logo.png" width="150" height="84">

<p></p>
<p></p>
<p></p>
<a id="Colliery" href="Colliery.html" ><span>Colliery</span></a>
</header>


<div id="maincontent">
<img src="Images/Architecture/Colliery/IMG_2997.jpg" width="800" height="800">
<img src="Images/Architecture/Colliery/IMG_3002.jpg" width="800" height="800">
<img src="Images/Architecture/Colliery/IMG_3057.jpg" width="800" height="1043">
</div>
</div>





CSS Code:


#container {
width: 1000px;
/* max-width: 98%;*/
margin-left: 230px;
margin-right: auto;
position: relative;
min-height: 100%;
}
#sidebar {
position: fixed;
z-index: 20;
width: 180px;
padding-top: 100px;
padding-left: 50px;
/* text-align: left;*/
left: 0;
border-style: none;
}
#maincontent {
padding-right: auto;
padding-top: 100px;
}

img {
border-style: none;
}

#Colliery {
display: block;
width: 140px;
height: 14px;
background: url("../Images/Colliery.gif") no-repeat 0 0;
}

#Colliery:hover {
background-position: 0 -14;
}

#Colliery span {
position: absolute;
top: -999em;
}



I"ve pasted all of the code just in case there's some conflict with the page layout and the rollovers....

The original image is 140px long x 28px high.

Any help really appreciated.
Cheers/
21-Dec-11 18:06
@nickworley
What's up brother? Nice pictures!
I scoped your CSS that you posted and then the CSS on the actual site. Are you sure the "hover" declaration for Colliery is in the CSS on the site? I didn't see one.
21-Dec-11 18:12
@cwalkdawg

Thanks for the reply.

I actually solved it tonight. I definitely had the hover declaration for Colliery - the problem appeared to be in the pixel movement. I definitely also had that correct, checked it several times, but saw another example using center bottom. Changed it to that and it worked.

Now need to get my head around a DWT for structuring the site / applying changes across the board later down the line as I add more projects.

Thanks for the shout on the images.
05-Jan-12 12:18
Hi there! First of all: great tutorial. I'm an old school web designer and I am always struggling moving to old html/tables/javascript stuff to a cleaner css life.

Thanks again!!

[Edited by grimorg80 on 05-Jan-12 12:23]
11-Jan-12 17:05
@grimorg80: Thanks for the feedback - I'm glad you liked the tutorial
19-Mar-12 23:38
Great tutorial. I am trying to combine this tutorial with this one here: http://www.elated.com/articles/html-form-buttons/ but having a challenging time.

Basically, I have a form with 3 rollover buttons. Each button has a unique name, i.e forward, reverse, add_user. The form calls a php file called processor.php and within processor.php, depending on what was clicked, the appropriate action happens with the processor.php file.

I cannot get both the rollover image and the multiple buttons to work on the same page. for some reason, the $_POST['add_user_x'], $_POST['forward_x'],$_POST['reverse_x'] are all empty when I use a rollover, but work fine when I do not use a rollover....

Any ideas on how I can merge both of your techniques?

Thanks!
23-Mar-12 03:41
@bullmoose: You'll need to post the URL of your form page so we can see the problem.
23-Mar-12 08:10
@matt, thanks for the response and here is the page:
http://www.speedylines.com/test/store_console.php

Regards,
bullmoose
27-Mar-12 00:21
@bullmoose:


<a id="reverse" href="processor.php" class="classname" input type="image" name="subtract" title="Subtract"><span>Subtract</span></a>


isn't valid markup.

You need to give a <button> element an id, then select the element in the CSS using its id.

eg:


<button type="submit" id="reverse">text</button>


Your CSS needs to style the element by removing all borders and outlines, then it needs to add the background image sprite, as in the original tutorial. It's not always possible to get this looking good on all browsers, especially older ones. You may be better off using regular <a> elements and attaching JS click handlers to them to submit your form.

View all 183 responses »

Post a response

Want to add a comment, or ask a question about this article? Post a response.

To post responses you need to be a member. Not a member yet? Signing up is free, easy and only takes a minute. Sign up now.

Top of Page