Snazzy Hover Effects Using CSS

Snazzy Hover Effects Using CSS

With all these CSS3 effects and tutorials popping up every day that show all the new and wonderful things we can make happen, we sometimes forget about poor little old CSS2.1 and the great potential it still has.

With very good browser support, we can do lots of cool things that we know will work in all major browsers.

In this tutorial, I will be going over creating flexible advanced hover techniques using CSS2.1 properties.

Here is a live demonstration of the effect we will be creating.

Advanced hover states are quite simple

When I first started learning CSS, the :hover pseudo-element was no more than a way to remove the underline on a text link.

Since then, through experimentation, I have learnt that it is so much more powerful and it can create some really cool effects when used in conjunction with other CSS properties.

One of these effects is creating caption text to appear over the top of images, creating some nice visual feedback for the user and giving them some information about the image they’ve moused over or focused on.

The astute reader will see that this technique has great potential outside of what we discuss here, such as showing CSS tool tips when hovering over a hyperlink.

We’ll be using these key CSS property, pseudo-classes, and pseudo-element to accomplish our technique:

Generated content is king

CSS-generated content allows us to append (:after) or prepend (:before) content to an element that can display hard-coded content, dynamic content (attr()), images (url()) and counters (counter()).

In this example, we’ll utilise the attr() function to extract the title attribute from the anchor (<a>) tag.

    <a href="#" title="Sunrise on the farm">
        <img src="img01.jpg" width="200" height="206" alt="Beautiful sunrise" />
ul a:hover:after
    content: attr(title);

Basic image caption using generated content

As you can see from above, the mark-up is very simple and the use of the content property allows us to create some great behaviour without the bloat. All it’s doing here is getting the anchor’s title attribute value and appending the content :after the anchor.

Make it sit nice

Now that we have some simple mark-up and are now displaying our caption on hover/focus after the image, we’ll add some extra CSS to pretty up the caption and make it sit over the image, rather than beneath it.

We first do some simple styling to the containing list item. The only style required here is the relative position. Using a relative position allows us to absolutely-position elements inside the list item.

The other styles are decorative.

ul > li {
    position: relative;
    float: left;
    list-style: none;
    margin: 0 20px 20px 0;
    font-size: 10px;

Adding some more styles to our :hover pseudo-class, we absolutely position the CSS-generated content and give it a height, background, line height (this is the same as the height, so we can vertically-centre the text).

We also do the same for the :focus pseudo-class so someone who can’t use a mouse still gets the full experience.

ul a:hover:after,
ul a:focus:after
    background: rgb(255,255,255);
    bottom: 2px;
    content: attr(title);
    color: #000;
    display: block;
    font-weight: bold;
    height: 30px;
    line-height: 30px;
    position: absolute;
    text-align: center;    
    width: 100%;

We’ll also add an outline around the image to create a nice border effect when the user focuses or hovers on it.

The reason we don’t use the border property is because outline doesn’t affect the area it "outlines", whereas border will affect the element.

ul a:hover img, ul a:focus img { outline: 3px solid #ccc; }

Basic styled caption using generated content

We now have a simple and effective caption overlay, which has very lean mark-up and some simple CSS to create an effective technique.

Let’s extend it further

We have a simple caption overlay that works nicely, but how about we add some additional styles so we can change the position of the caption very easily by adding a couple lines of CSS?

We will create a .reverse class that we can assign to any of our anchor elements that will reposition our caption to appear at the top of the image rather than the bottom.

ul a.reverse:hover:after,
ul a.reverse:focus:after { top: 0px; }

Change caption position using minimal CSS

We can also create an .offset class to position the caption in the middle or offset it by any value. The negative margin is used to pull it into the middle.

ul a.offset:hover:after,
ul a.offset:focus:after { top: 50%; margin-top: -15px; }

Another example of changing caption position using minimal CSS

A sprinkle of CSS3

Of course, I couldn’t resist using some CSS3 goodness to make it that little bit nicer. At this point, we’re just using the principles of Progressive Enhancement where browsers that are CSS3-capable will have a slightly better experience. Our effect will still work on CSS2-capable browsers (such as Internet Explorer) even after we’ve spruced it up with CSS3.

On the anchor I’ve added the CSS3 box-shadow property to create a nice drop shadow effect when the image is hover/focused on. The outline is set to none so that the dotted outline won’t appear when it’s focused on.

(Check out more CSS3 techniques.)

Normally this would be frowned upon, but since we are adequately showing that the image has focus by showing the caption, outlining the image and applying the drop shadow, it’s still obvious that the element in question has focus.

Note: It should be said that the box-shadow property has officially been removed from the W3C CSS3 draft specification for further discussion, however browser vendors appear to have no plans of removing it any time soon, if ever.

ul a:hover, ul a:focus
    display: block; 
    outline: none; 
    -moz-box-shadow: 3px 3px 5px #000; 
    -webkit-box-shadow: 3px 3px 5px #000; 
    box-shadow: 3px 3px 5px #000; 

Using CSS3 we can improve the look for the better browsers

Using the CSS3 rgba() property, we can make the background slightly opaque so that the image can be seen beneath our caption. Setting rgb before our rgba value ensures that browsers such as IE8 still show a background.

ul a:hover:after,
ul a:focus:after
    background: rgb(255,255,255);
    background: rgba(255,255,255,0.7);

We can also add a CSS gradient in Firefox 3.6, Safari 4 and Chrome 4. (See another tutorial on using CSS gradients on your web typography.)

In the gradient, we also use rgba so that we can have an opaque gradient that blends between two colours.

background: -moz-linear-gradient(top, rgba(255,255,255,0.7), rgba(204,204,204,0.7)); /* Firefox 3.6+ */
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,255,0.7)), to(rgba(204,204,204,0.7))); /* Safari/Chrome */

To make the caption text pop out a little more, I’ve added the text-shadow property to give it a subtle drop-shadow on the text.

text-shadow: 1px 1px 1px #fff;

Using more CSS3 to add transparency, gradients and text shadows

Another CSS3 property that is quite useful is box-sizing, which lets us adjust how the box model works.

ul a.alternate01:hover:after,
ul a.alternate01:focus:after
    top: 0;
    width: 50%; 
    height: 100%; 
    line-height: normal; 
    text-align: left; 
    padding: 4px; 
    font-size: 12px;
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;

By adjusting the box model, the technique can further extend the caption by utilising box-sizing to our advantage, and so we can keep this technique as flexible as possible by making the overlay cover half the image and have the content run down the side.

With a few adjustments, it’s quite simple using this technique.

Using more CSS3 we can adjust the box model and have vertical captions

The box model—by default—adds padding and borders on top of the height that is set, and since we are setting our height to a 100%, this will essentially push out our overlay by a few pixels.

To change that behaviour, we utilise the CSS3 box-sizing property and set it to border-box. This allows us to set our percentage value and have any padding and borders calculated in conjunction with our height, so as not to push it out like it naturally does.

By default box-sizing is set to content-box. IE8 obviously doesn’t support this property, however IE’s box model naturally behaves like it was set to box-sizing: border-box and therefore doesn’t need any special treatment to work.

Mozilla and WebKit based browsers require their specific prefixes, whereas Opera supports it without any need for a prefix.

ul a.reverse:hover:after,
ul a.reverse:focus:after { top: 0; right: 0; }

We can also reverse the position of the vertical caption

With a small addition to our original reverse class, adding right: 0, we can create some reusable CSS to reverse it either horizontally or vertically without affecting either of the anchors that have the .reverse class. Depending on the caption type— horizontal or vertical—the .reverse class will handle either situation correctly.

It wouldn’t be CSS if IE didn’t do something funky

Of course, IE8—ironically, the first IE browser to support the full CSS2.1 spec—has got an issue with generated content.

Upon initial testing, I thought IE8 didn’t actually support generated content as when I hovered over the image, nothing was showing up.

After some testing and using a text colour that contrasted with the image, I noticed that the background colour wasn’t showing up, but the text was.

So then, I thought at least it still works, just not with a background. Not content with that, I investigated further and discovered that the background colour was appearing behind the image. Don’t ask me how or why this is happening.

I came up with a solution that fixes this issue and allows the CSS-generated content and its background to appear over the image like it does in the other browsers.

Basically, we give the image a relative position and set the z-index to -1 to force it under the generated content.

We then have to reset the position back to static for the better browsers as position relative causes havoc with the caption overlay. I utilised the CSS3 not() pseudo-selector—which IE8 doesn’t support—and set the position back to its default behaviour of static.

ul li img { position: relative; z-index: -1; } /* For IE8 */
ul li:not(#foo) img { position: static; } /* Reset position for better browsers */

Putting it all together

Let’s put it all together into a fully working version.

You can see a demonstration of the snazzy hover effects using various images sizes to show its flexibility.

Here’s the full markup required to create a single image with caption overlays. The demo page has more images on it, as well as demonstrating changing colours and shifting the overlaying caption into different positions as explained earlier in the article, just to show various potential tweaks and use cases.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "">

<html xmlns="">

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />	
    <link rel="stylesheet" type="text/css" href="_styles.css" media="screen" />	
    <title>Snazzy hover effects using CSS demo</title>

            <a href="#" title="Sunrise on the farm">
                <img src="img_sunrise.jpg" width="300" height="200" alt="Beautiful sunrise" />


And the CSS to transform the simple markup into a snazzy hover/focus effect.

ul { overflow: hidden; padding: 5px; }
ul > li {
    position: relative;
    float: left;
    list-style: none;
    margin: 0 20px 20px 0;
    font-size: 10px;
ul a { text-decoration: none; display: block; }

ul li img { display: block; position: relative; z-index: -1; } /* IE8 fix, background colour appears behind img for uknown reason set negative z-index */
ul li:not([class=na]) img { position: static; } /* Reset relative position, as this plays havoc with good browsers */

ul a:hover, ul a:focus 
    display: block; 
    outline: none; 
    -moz-box-shadow: 3px 3px 5px #000; 
    -webkit-box-shadow: 3px 3px 5px #000; 
    box-shadow: 3px 3px 5px #000; 
ul a:hover img, ul a:focus img { outline: 3px solid #ccc; }

ul a:hover:after,
ul a:focus:after
    content: attr(title);
    color: #000;
    position: absolute;
    bottom: 0;
    height: 30px;
    line-height: 30px;
    text-align: center;
    font-weight: bold;
    width: 100%;
    background: rgb(255,255,255);
    background: rgba(255,255,255,0.7);
    background: -moz-linear-gradient(top, rgba(255,255,255,0.7), rgba(204,204,204,0.7)); /* Firefox 3.6+ */
    background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,255,0.7)), to(rgba(204,204,204,0.7))); /* Safari */
    display: block;
    text-shadow: 1px 1px 1px #fff;

Wrap up

We now have a simple technique that’s very capable using only CSS2.1. Of course, I also showed how to spruce it up with some additional—but not critical—CSS3 to make it a little nicer in more modern browsers.

We have good browser support; it works in all the latest release browsers.

And we made sure the technique was keyboard-accessible so that if you don’t or can’t use a mouse, the technique still works.

What do you think? Will you be using any of the techniques I’ve mentioned above? Why or why not? Share your thoughts and questions in the comments.

Download Source Files

Related Content

About the Author

Ryan Seddon is front-end developer based in Melbourne, Australia. He loves to tinker with CSS, JavaScript and coming up with new techniques. You can find his discoveries and articles at his blog, The CSS Ninja. You can also connect with him through Twitter: @thecssninja.

This was published on Apr 15, 2010


Manmohanjit Apr 15 2010

<3 css.
Good stuff.

Keith Apr 15 2010

“We have good browser support; it works in all the latest release browsers.” not for IE6.

well, IE6 is not the latest. ^_^

Marco Apr 15 2010

Pretty neat to see, well done!

You could also add some “CSS Transitions” (supported by -webkit browsers en the latest dev version of FF) to make the text “slide” in (or fade in, whatever you need)!

Great example use of CSS.

Charlie Apr 15 2010

Really nice – it’s all about CSS :D

HD Guy Apr 15 2010

Looks great!

Keevin Apr 15 2010

Really amused of what CSS3 is capable, great article!

Claudio Apr 15 2010

It’s a really nice effect!

Thank’s for this great tutorial Ryan.

Webstandard-Blog Apr 15 2010

Very interesting Ryan, but using ul for an image and the image description isn’t really semantic, right?

wsvap Apr 15 2010

Very nice, but until it doesnt work well in any IE, u can forget it :(

wsvap Apr 15 2010

btw, i think using ul for a list of images is not a problem :)

Leo Horie Apr 15 2010

Looks nice in Firefox, but it’s unusably broken in Flock :(
So much for Gecko

abhishek Apr 15 2010

Its the broadest article i have found related to image and css. I am going to test it all and apply on my blog.
Thanks for providing information.

really nice effect man

Jane Cook Apr 15 2010

wow…what a great job

James Apr 15 2010

Doesn’t work in IE6. Rubbish.

Brady J. Frey Apr 15 2010

Similar to what i did on my portfolio:

But I used an extra semantic tag to shift into position on hover and image replace.

steve Apr 15 2010

Very nice effect, thanks a lot

Beben Apr 15 2010

hmmm….a:hover can be caption?
its awesome, thanks ^_^’

how can anyone say this is a nice effect? It doesn’t work in all browsers.

stygyan Apr 15 2010

The worst about it: Browsers keep showing tooltips even when the caption is on screen.

Why don’t they let us designers disable it, huh?

Mark Johnston Apr 15 2010

@Webstandard-Blog are you saying that you can’t have a list of one thing? Maybe that is one image tile of many? The semantics should be defied by it’s context, something which you don’t really know from this tutorial.

suhni --- Apr 15 2010

swift clean tutorial – thanks

very nice. i like the hover style in the picture.

Thanks for all the great responses!

@Webstandard-Blog – Correct using it for just 1 image wouldn’t be very semantic but on the demo page I have 4 in each ul so I think that’s ok. If you had just 1 image then the ul could easily be replace with more semantic markup such as a paragraph or span.

@Keith, @wsvap – The reason it doesn’t work in IE6/7 is because neither of them fully support the CSS2.1 spec and this technique relies on the content property. A degraded experience is a very acceptable solution in my books.

Daniel15 Apr 15 2010

Awesome effect. Very nice! :D

@Marco: Opera supports transitions too, via -o-

@Webstandard-Blog: It’s perfectly semantic – It’s a list of images. :)

wsvap Apr 16 2010

It doesn’t work well in IE8 to! And the big part of pages are still visited in this 3 browsers now (IE6-7-8) :(
The article is good, thx :) But I think we have to wait at least 1 year to use it in a corporate page.
IE9 is on the way, maybe it will solve the problems?!
(sorry for bad english)

Thanks Ryan!

Great article and cool css tricks! I do have to ask everyone’s opinion though… wouldn’t it be better to use the alt attribute’s content, as these are descriptive pieces; and therefore leave the title attribute to contain something like what Brady does on his site with a directional reference i.e. “view the project/image”? Wouldn’t this also open the use to the visitors who, for whatever reason, can’t see the images in your page.

I offer Roger Johanson’s great article on the use of “The Alt and Title Attributes” at 456BereaStreet as reference.[]


Unbelievable effect – it will save massively on server time loading and give a better user experience all round.

Is this cross browser compatible?

CSSReX Apr 16 2010

I hope its compatible with every major browser including IE6!

Triomatt Apr 16 2010

Nice CSS work but the another issue is that iPhone/iPad users will get no information at all about the images because the hover event is not supported on those devices. Not a deal-breaker but nothing to ignore either. I realize that getting this to work across IE 7-8 is paramount but bagging on him about IE6? I would re-work it so it degrades gracefully for users of that antiquated rubbish but getting to work in IE6 is a waste of time.

Great article! It’s good to be reminded of what can be done with CSS 2, thanks!

lava360blog Apr 16 2010

if its not supported by IE6 i think its just ok.. but the tutorial is great. thanks Ryan Seddon

Oyun Kit Apr 16 2010

nice work. congratulations.

Maicon Sobczak Apr 16 2010

Very useful tutorial. The elegance of CSS is amazing!

Does every single tutorial article these days need to degrade into an IE6 debate? People, let it alone already. We all know the deal with that browser. If you need to support it, do it, and stop whining. What a downer it is to see all your negative comments on this post. A tutorial designed to take advantage of modern technology will not, by definition, support old technology.

Ryan, I’m happy to see the advanced selectors in tutorials like this. I noticed you used this selector: ul > li. What does using the child selector accomplish that ul li wouldn’t? I can’t think of a situation in which I would have an li element that is not a direct descendant of ul.

@Jon – Glad you asked about the alt attribute and there is very good reason why I didn’t use it. img elements are what’s considered a replaced element in the box model (form inputs fall under this too). Replaced elements do not have :before and :after pseudo-classes available to them. Therefore you can’t actually do the generated content trick and is the reason I used the title attribute to accomplish this technique.

@Triomatt – Yes touch devices are completely different and I think we need a new pseudo-class to cater for this issue something like :touch would be very useful and would trigger similar to how mousedown works in the javascript event model.

@IE6/7 lovers – IE6/7 support can be added but would require conditional comments and extra markup. If there is interest I can post an example of doing that.

thank you
really useful tips

Askar Sabiq Apr 17 2010

great tuts ! i like the transparency :D

Phil E. Drifter Apr 17 2010

Too bad you don’t have at least one working example demonstrating the code at hand.

@Ryan – Yeah you are right there is no advantage of using ul li in this demo over the direct child selector.

That’s some neat stuff! It seemed like CSS3 was the only thing that was worth looking into.

Jascha Apr 19 2010

Very nice, but can it be transparant to, that would be even nicer :)

Andrew Roberts Apr 19 2010

Very cool! Gonna have to try these out, thanks!

sir jorge Apr 21 2010

that’s easier than I thought

Juegos Apr 28 2010

Great tutorial and trick, love it.

MicroAngelo Jul 01 2010

Very nice.

Also you might be interested to check out the funky way they use :hover:after for the “tweet this” link at the top of Reeder’s page:

Obviously they’ve added some CSS3 transitions, but the basic idea is have a grayscale copy of an image as an element’s background with enough padding so you can see it, then add a colour version of the same image using :after with opacity:0 so it covers the background image, and finally swap (or in this case transition) to opacity:1 on hover.

Very clever. I did a double-take when I first saw it!

Brett Widmann Nov 30 2010

This is a great tutorial! I love the effect. Thanks for sharing.

naughtyric Jan 23 2011

great tutorial, very useful thanks :)

Holly Feb 08 2011

Could you explain the use of :not() more? I’m really struggling with the IE fix.
I’ve be fiddling for hours but i can’t seem to get it working, perhaps it’s because i’m altering the elements within the id #portfolio. At the moment i have:

#portfolio ul li img { display: block; position: relative; z-index: -1; }
#portfolio ul li:not(???) img { position: static; }

no matter what i put in the brackets i can’t get it working. The image just displays but there’s no link or hover effect whatsoever. Work beautifully in no IE browsers though. But i’m really stumped here?

Thank you for the lovely tutorial and any help!

Abhishek Dwivedi Mar 23 2011

Great writeup! Thanks a lot for all this info..


oh can anyone tell me how would I do this?
I wanna hide elements belong to another class when I hover on another class. like this


if I put display:none directly within classA:hover I can make it hide whenever I hover the mouse over the classA elements. but I don’ wanna do that. I just want to hide them when I hover the mouse on classA elements.

this looks doesn’t work. :(

Anita Mar 27 2011

Tx u for very much to offer this tutorial. Much appreciated and I tried very hard to find this effect.

To those who are critical that it did not work on the old IE, u should be critical to MS instead of the author and please take the time to crap MS IE. To those with unsupported browser, what percentage are they since this is 2011. And for IE security concerns, get rid of IE. How would u provide info on an image while saving on real estate? Thus my reason for searching on this design. tx

Myth Thrazz Apr 13 2011

Thanks for the great tutorial!

with just a small change from:
content: attr(title);
content: url(img_hover.png);

I got custom overlay image on my gallery miniatures. And without any JS! Fantastic job.

And many thanks.

Just a note that there should be a very slight adjustment to the list item. It should have z-index: 1; applied to it. Otherwise if the list appears within a container that has a background colour in IE8 it will appear behind it the demo has been updated.

neelhaam May 27 2011

Hay it really good & work fine you can post an ad click my name.

Brett May 27 2011

Thanks for the great tut; however no matter what I do I cannot get it to work in IE8 even after setting the z-index to negative etc….. all I see is the text. :(

Pretty neat, I gave it a shot, and it worked great.
I decided to alter the display property of a span element nested inside my links instead – because I couldn’t stand the browser generated tooltip, which simply re-states the contents of the ‘title’ attribute.

As far as I learned, the tooltip is a browser-dependent element and therefore there is no way of getting around it’s display without resorting to Javascript.

Still, I can see myself relying on this for image overlays.

Thanks Ryan!!! Definitely going to use this!!!

Daniel List Jul 07 2011

Thank you for such a great tutorial, however I am running into some IE issues.

I have the effect working lovely on FF with a semi transparent box and black text. However in IE it doesn’t show a background color for the div.

I created an IE.css stylesheet which is conditionally referenced, and set the background-color to white, which doesn’t work.

Strangely enough, if I move the div from over the top of the image to underneath it, then the background colour will show.

Anyone have any idea what the problem is?

Billy Walker Oct 07 2011

I got around the IE Fix by directly defining my anchor class a high z-index, as all of the hover attributes was defined by this class.

I then gave the img within this anchor a negative z-index to push it behind the rest of the class.

a.class {position:relative; z-index:2000;}
a.class img {position:relative; z-index:-1}

worked like a charm, although in IE the transparency has gone but i can live with that.

Time to finish my glass of wine!

Somer Sudduth Oct 26 2011

It is really a great and helpful piece of info. I am glad that you just shared this useful tidbit with us. Please stay us informed like this. Thanks for sharing.

ahmad balavipour Oct 31 2011

Thanks alot,

very good tutorial

Joesanti Nov 20 2011

Now, I don’t have to use jquery to get such cool transition. Thank you!

Digital Dreams Aug 28 2012

I am able to do dynamic work without Photoshop and Jquery

JTony Aug 29 2012

I really have to thank you. This was exactly the information I was looking for. After fighting with javascript and other less complete explanations in other places, this allowed me to complete my process in about an hour after fighting with it for a whole day. Thank you!

This comment section is closed. Please contact us if you have important new information about this post.