CSS drop-shadows without images

Drop-shadows are easy enough to create using pseudo-elements. It’s a nice and robust way to progressively enhance a design. This post is a summary of the technique and some of the possible appearances.

Update [17 December 2010]: Demo now includes a technique to allow for CSS transforms on the parent element.

Demo: CSS drop-shadows without images

Known support: Firefox 3.5+, Chrome 5+, Safari 5+, Opera 10.6+, IE 9+

I’ll be looking mainly at a few details involved in making this effect more robust. Divya Manian covered the basic principle in her article Drop Shadows with CSS3 and Matt Hamm recently shared his Pure CSS3 box-shadow page curl effect.

After a bit of back-and-forth on twitter with Simurai, and proposing a couple of additions to Divya’s and Matt’s demos using jsbin, I felt like documenting and explaining the parts that make up this technique.

The basic technique

There is no need for extra markup, the effect can be applied to a single element. A couple of pseudo-elements are generated from an element and then pushed behind it.

.drop-shadow {
   position:relative;
   width:90%;
}

.drop-shadow:before,
.drop-shadow:after {
   content:"";
   position:absolute;
   z-index:-1;
}

The pseudo-elements need to be positioned and given explicit or implicit dimensions.

.drop-shadow:before,
.drop-shadow:after {
   content:"";
   position:absolute;
   z-index:-1;
   bottom:15px;
   left:10px;
   width:50%;
   height:20%;
}

The next step is to add a CSS3 box-shadow and apply CSS3 transforms. Different types of drop-shadow can be produced by varying these values and the types of transforms applied.

.drop-shadow:before,
.drop-shadow:after {
   content:"";
   position:absolute;
   z-index:-1;
   bottom:15px;
   left:10px;
   width:50%;
   height:20%;
   -webkit-box-shadow:0 15px 10px rgba(0, 0, 0, 0.7);
   -moz-box-shadow:0 15px 10px rgba(0, 0, 0, 0.7);
   box-shadow:0 15px 10px rgba(0, 0, 0, 0.7);
   -webkit-transform:rotate(-3deg);
   -moz-transform:rotate(-3deg);
   -o-transform:rotate(-3deg);
   transform:rotate(-3deg);
}

One of the pseudo-elements then needs to be positioned on the other side of the element and rotated in the opposite direction. This is easily done by overriding only the properties that need to differ.

.drop-shadow:after{
   right:10px;
   left:auto;
   -webkit-transform:rotate(3deg);
   -moz-transform:rotate(3deg);
   -o-transform:rotate(3deg);
   transform:rotate(3deg);
 }

The final core code is as shown below. There is just one more addition – max-width – to prevent the drop-shadow from extending too far below very wide elements.

.drop-shadow {
   position:relative;
   width:90%;
}

.drop-shadow:before,
.drop-shadow:after {
   content:"";
   position:absolute;
   z-index:-1;
   bottom:15px;
   left:10px;
   width:50%;
   height:20%;
   max-width:300px;
   -webkit-box-shadow:0 15px 10px rgba(0, 0, 0, 0.7);
   -moz-box-shadow:0 15px 10px rgba(0, 0, 0, 0.7);
   box-shadow:0 15px 10px rgba(0, 0, 0, 0.7);
   -webkit-transform:rotate(-3deg);
   -moz-transform:rotate(-3deg);
   -o-transform:rotate(-3deg);
   transform:rotate(-3deg);
}

.drop-shadow:after{
   right:10px;
   left:auto;
   -webkit-transform:rotate(3deg);
   -moz-transform:rotate(3deg);
   -o-transform:rotate(3deg);
   transform:rotate(3deg);
 }

No Firefox 3.0 problems this time

Some pseudo-element hacks require a work-around to avoid looking broken in Firefox 3.0 because that browser does not support the positioning of pseudo-elements. This usually involves implicitly setting their dimensions using offsets.

However, as Divya Manian pointed out to me, in this case we’re only using box-shadow – which Firefox 3.0 doesn’t support – and Firefox 3.0 will ignore the position:absolute declaration for the pseudo-elements. This leaves them with the default display:inline style. As a result, there is no problem explicitly setting the pseudo-element width and height because it won’t be applied to the pseudo-elements in Firefox 3.0.

Further enhancements

From this base there are plenty of ways to tweak the effect by applying skew to the pseudo-elements and modifying the styles of the element itself. A great example of this was shared by Simurai. By adding a border-radius to the element you can give the appearance of page curl.

.drop-shadow {
   -moz-border-radius: 0 0 120px 120px / 0 0 6px 6px;
   border-radius: 0 0 120px 120px / 0 0 6px 6px;
}

I’ve put together a little demo page with a few of drop-shadow effects, including those that build on the work of Divya Manian and Matt Hamm.

If you’ve got your own improvements, please drop them in a jsbin and send them to me on twitter.

35 comments

#

Nicolas says…

@Divya: I added a paragraph about Firefox 3.0 and why there is actually no need for any work around with this specific effect. It ignores position:absolute on the pseudo-element and so the display value is left as the default inline…meaning it won’t apply width and height values to the pseudo-elements anyway.

#

Nicolas says…

@Spencer: Thanks. If you want to reproduce that exact effect you could make the code a bit more efficient and robust. I’ve added the non-webkit transforms and box-shadows too:

ul.box {
   position: relative;
   z-index: 1;
   overflow: hidden;
   list-style: none;
   margin: 0;
   padding: 0;
}

ul.box li {
   position: relative;
   float: left;
   width: 204px;
   height: 113px;
   padding: 0;
   margin: 0 30px 30px 0;
   background: #fff;
   -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.06) inset;
   -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.06) inset;
   box-shadow: 0 1px 4px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.06) inset;
} 

ul.box li:before,
ul.box li:after {
   content: "";
   z-index: -1;
   position: absolute;
   right: 10px;
   bottom: 10px;
   width: 70%;
   height: 55%;
   max-width: 300px;
   -webkit-transform: skew(-15deg) rotate(-6deg);
   -moz-transform: skew(-15deg) rotate(-6deg);
   -o-transform: skew(-15deg) rotate(-6deg);
   transform: skew(-15deg) rotate(-6deg);
   -webkit-box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
   -moz-box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
   box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
} 

ul.box li:after {
   right: auto;
   left: 10px;
   -webkit-transform: skew(15deg) rotate(6deg);
   -moz-transform: skew(15deg) rotate(6deg);
   -o-transform: skew(15deg) rotate(6deg);
   transform: skew(15deg) rotate(6deg);
   -webkit-box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
   -moz-box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
   box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
}
#

tkoc says…

Thank you, Nicolas. Excellent walk-through. Took the liberty of tipping a few of my friends of your site.

Merry Xmas 2 U

#

virt says…

That’s an awesome tip. thanks man!

#

David says…

This is excellent – thanks! I’ve started using these on a couple sites already. I’ve noticed though that the inset shadow makes webkit-based browsers slow to a crawl on the mac, making the page almost unusable. Things like resizing, filling in forms etc. become painfully slow until I disable the shadow. I haven’t figured a way around it yet – anyone have the same problem?

#

Gidon says…

IE9 RC also supports transform (-ms-transform: )

#

Greg Babula says…

Great tip, thanks for sharing

#

NICCAI says…

These are very cool proof of concepts, but I put together a demo that illustrates webkit having major issues with repeated occurrences of shadows with large blur values (I originally thought the problem was attributed to the double box-shadows as you’ll see by the title :D ).

http://niccai.com/demos/webkit/webkit-double-box-shadow-bug.html

#

theWebalyst says…

Valuable post Nicolas, thanks. I’m not clear whether non-supporting browsers degrade gracefully and interested to hear your response to @David about the Mac (something I can’t test myself).

Mark

#

Raghav says…

cool need more patience to work these kind of effect
very nice tut.

Thanks

#

Nicolas says…

@theWebalyst: See the comment just above yours. And yes, degrades gracefully in older browsers. Open the demo up in IE8 to see for yourself.

#

Gaurav Mishra says…

Neat!
Will explore that more very soon.
Thank you!

#

Jarod Billingslea says…

That’s cool. All you did was use the pseudos After and Before to pull this effect off haha. Nice trick there.

#

Galen Gidman says…

These look great Nicolas. I implemented something like this on my CSS3 Sticky Note, but I don’t think they look as good as these. Probably just need a little more tweaking.

#

Darron Driver says…

Nice work Nicolas.

You could also leverage the same technique to build speech bubbles, although you’d be quite limited with their presentation from having to have the pseudo element on top of the parent.

Example: http://jsfiddle.net/yTXz4/

#

Nicolas says…

Hey Darron. I did some pseudo-element speech bubbles a while back – http://nicolasgallagher.com/pure-css-speech-bubbles/. Most of those use CSS 2.1 but you can also use CSS gradients (to create the triangle of colour) and CSS transforms (to modify the shape and orientation). And there is always the option of using the pseudo-element to display an image of a more decorative “tooltip” too.

#

Alan says…

Have you run across any issues when applying a CSS drop shadow to a div containing a video? I recently applied the lifted corners style and for some reason the video suddenly has frame rate issues but only in Chrome. It works in every other browser as far as I can tell. Perhaps I implemented incorrectly? You can check out the site over at http://realityeaster.com

#

Nicolas says…

Alan, I believe this is a WebKit issue with box-shadows (possibly confined to Mac OSX) that has been fixed and will eventually make it’s way into the a stable release of Chrome. There were no problems when I viewed your site in Chrome (Windows).

#

Alan says…

Thanks, Nicolas. Appreciate the follow-up. Seems odd that it’s only with Chrome (in Mac OSX). Good to know though.

#

shanoboy says…

Holy crap I love this. I can’t wait to test some of this out. Too bad it’ll still be a little while before most average users upgrade to IE9. Thanks for the post.

#

Chris Rowley says…

I really love these effects but I’m here to ask if there’s a known solution to Chrome not rendering the inset shadow over the box? In all of your examples Firefox looks perfect, and even IE9 seems to be showing everything alright, but Webkit is a toss-up. On this machine running Windows 7 64-bit even Safari 5 properly extends the shadow inside the box, but Chrome renders the box solid. It’s most obvious when you Ctrl-+ a few times, but even from 100% it is noticeable.

I also wanted to pass along a weird bug: in IE9 if you try to implement your effects in a table-layout div you can’t use border-collapse: collapse or only the inset shadows render. Weird?

Different but annoying: in every browser but Firefox using the same div table-cell layout (to achieve equal-height columns) with no border-spacing (as that puts outer border spacing, not just between the internal cells) the divs are perfectly aligned, but this causes a simple edge box-shadow to be covered up. For my example, I have three divs set to display: table-cell, the first column has a box-shadow set to the right side to help offset its background with the adjacent white background cell. Firefox properly continues to render the effect, but Webkit obscures it unless I make the second div’s background transparent. Any ideas what the final spec will entail regarding these inconsistencies? Personally the Firefox rendering appears to be correct, but what do I know lol?

#

Praharsh RJ says…

Nice information, i will definitely try it on my new projects

#

Blogger NCode says…

wow I like that…

#

Geoff says…

Awesome. I figured out a couple of methods similar to this for doing the basic “corner peel up shadow” a couple months back but boy does it help that you’ve gone and figured out all of these others!

#

Brandon says…

This is absolutely incredible. Thank you so much for this! You have save me a ton of time.

#

Nicolas says…

@Josh The article makes clear that this technique works in IE9+. It’s a non-critical decorative effect that can be included as a CSS-only enhancement in capable browsers.

#

Josh Lind says…

Agree it’s normally mostly a “non-critical decorative effect,” however we often use it to pop one specific item for call-to-action type elements for downloading trial software.

Just had to make it work in IE before we could roll with it.

Thanks for your work! It was a basis for what I did.

#

Bart says…

This is amazing!!! One tip for the Pure CSS3 box-shadow page curl effect demo…the overflow:hidden on the ul cuts off the left side shadows of the first two boxes. Remove the overflow altogether or use a different clearing method, or add left padding to avoid cutting the shadow.

#

Dan says…

This is wonderful! Thank you for sharing.

#

Hungrysquirrel says…

Thanks for sharing these design patterns. I will say that if the element you are placing the page “curl” effect on is not showing the shadow and you have double and tripple checked the markup and CSS…I encourage you to look at the parent element and make sure is is positioned relatively with a z-index.

Comment on this post

Please wrap code fragments in <code> tags, wrap blocks of code in <pre><code>, and use JsFiddle to post working examples.