Creating Beveled Images with CSS

Tweet

Recently, I wanted a simple CSS method for adding a beveled effect to images. It’s easy enough to create a sense of depth with normal outset borders (below left), but I was after an effect that would actually look like part of the image, as though it were a bevel on the image itself (below right).

Demo 2: The borders look like bevelling on the image itself.Demo 1: The image has gray outset borders around it.

In the end I found four different ways of doing it, each with different levels of support: from the cleanest approach that only worked in one browser, to the most robust that works in everything back to IE6.

All of them work on the same core principal; black borders for shade and white borders for highlighting are overlaid on top of an image, and then blended with some form of opacity. In each case, browsers without support for that technique will simply show the image as normal.

Technique 1: Using Generated Content on the Image (Demo)

  • Pros: Ultra-clean technique requires no additional markup
  • Cons: Only works in Opera

With this first technique we create a pseudo-element using :after, then style it to be perfectly overlaid on top of the image. Then we add borders to the overlaid element, and use RGBA to define each border color: the top and left borders are rgba(255,255,255,0.4), white with 40% opacity; and the bottom and right borders are rgba(0,0,0,0.4), black with 40% opacity:

img.beveled{    position:relative;}img.beveled:after{    position:absolute;    left:0;    top:0;    display:block;    content:"0a0";    box-sizing:border-box;    width:100%;    height:100%;    border:5px solid;    border-color:rgba(255,255,255,0.4)                 rgba(0,0,0,0.4)                 rgba(0,0,0,0.4)                 rgba(255,255,255,0.4);}
<img class="beveled" src="stormtroopers.jpg"     alt="A legion of Lego Stormtroopers, standing in formation." />

This technique only works in Opera because no other browser supports generated content on multimedia replacement elements like <img> and <object>. But since we’re only addressing Opera, we have the freedom to use box-sizing and 100% dimensions, rather than having to define the dimensions explicitly.(Note: the value of the content property in all these examples is a unicode non-breaking space. This is added because pseudo-elements have to contain something or they’re not rendered.)

Technique 2: Using Generated Content on a Wrapper Element (Demo)

  • Pros: Wider range of supported browsers
  • Cons: Requires additional markup and explicit dimensions

The second technique is essentially the same as the first, but this time we create the overlay element using generated content on a wrapper <span> for browsers that don’t support generated content on the <img> itself. For this technique we also need to begin defining explicit dimensions on the wrapper element and the generated content (although we could use vendor-specific versions of box-sizing on the generated content, we’d still have to define the dimensions of the wrapper, so we may as well do the same for both):

span.beveled{    position:relative;    width:200px;    height:200px;    display:block;}span.beveled:after{    position:absolute;    left:0;    top:0;    display:block;    content:"0a0";    width:190px;    height:190px;    border:5px solid;    border-color:rgba(255,255,255,0.4)                 rgba(0,0,0,0.4)                 rgba(0,0,0,0.4)                 rgba(255,255,255,0.4);}
<span class="beveled">    <img src="stormtroopers.jpg"         alt="A legion of Lego Stormtroopers, standing in formation." /></span>

Technique 3: Using Shadows instead of Borders (Demo)

  • Pros: The most visually attractive technique
  • Cons: Only works in Firefox 3.5 or later

The third technique is a diversion from the second, where instead of using RGBA borders we’re using -moz-box-shadow:inset to create the beveling effect. Since box shadow effects have an alpha gradient (rather than the same opacity at all points), the overall effect is much prettier and more rounded; and the spread radius parameter can be subtly used to blend away the corner sharpness.This effect is only supported in Firefox 3.5 or later; although Safari does implement box-shadow (as -webkit-box-shadow), there’s no support for inset:

span.beveled{    position:relative;    width:200px;    height:200px;    display:block;}span.beveled:after{    position:absolute;    left:0;    top:0;    display:block;    content:"0a0";    width:200px;    height:200px;    -moz-box-shadow:-5px -5px 2px rgba(0,0,0,0.4) inset,                    5px 5px 2px rgba(255,255,255,0.4) inset;}
<span class="beveled">    <img src="stormtroopers.jpg"         alt="A legion of Lego Stormtroopers, standing in formation." /></span>

I suppose we could have added a -webkit version anyway, in the hope of forward compatibility, but that would be a risk since we can’t know what any future implementation will be like — it might be worse than nothing!

Technique 4: Everybody’s Happy (Demo)

  • Pros: Works in all modern browsers
  • Cons: Requires more additional markup and explicit dimensions

The fourth and final technique is the most cross-browser compatible, but also requires the most HTML modification. It’s essentially the same as the second technique, but with two important differences: firstly, it uses a second physical <span> rather than generated content; and secondly, it uses ordinary hex border colors rather than RGBA, and then blends the whole element with opacity. Even Internet Explorer can handle this:

span.beveled{    position:relative;    width:200px;    height:200px;    display:block;}span.beveled span{    position:absolute;    left:0;    top:0;    display:block;    width:190px;    height:190px;    border:5px solid;    border-color:#fff #000 #000 #fff;    filter:alpha(opacity=40);    opacity:0.4;}
<span class="beveled">    <img src="stormtroopers.jpg"         alt="A legion of Lego Stormtroopers, standing in formation." />    <span></span></span>

Further Development

You could take this further by using colorful borders for a gel-like effect, or even multiple staggered overlays to create effects that are more subtle or intricate.But the basic idea is here, and I hope you find it useful. It’s certainly been great fun to play with!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://autisticcuckoo.net/ AutisticCuckoo

    Is content:"u00a0" correct? That looks like a JavaScript Unicode escape to me. Shouldn’t the CSS equivalent be content:"a0"?

    Other than that, an interesting technique!

  • LizardKing

    Beveled edges? Thats like soooooo 90’s

  • http://boelsterli.biz Pozor

    Recentry i stumbled over this:
    CVI-Labratory
    Different effects including bevel are applied by js and css.

  • http://www.brothercake.com/ brothercake

    @Tommy – you’re absolutely right, it should be “0a0″, which I assume is what you typed but WordPress parsed it .. that’s exactly what happened in the original post, and in my haste to fix it I fixed it incorrectly [but now fixed].

    @LizardKing – what am I Laurence Llewelyn-Bowen? I just implement stuff, and leave it to designers to worry about what looks good :P I’ll leave it to you to find a more aesthetcally agreeable use for this :)

  • http://www.magain.com/ Matthew Magain

    I had to google Laurence Llewelyn-Bowen to find out who he was. Wikipedia informs me that:

    He is noted for his flamboyant personality and for his dandyish appearance.

    I think with that context, Lizard King could be excused for confusing you with him. :-D

  • http://www.brothercake.com/ brothercake

    Here’s a variation that creates a simple two-stage dropshadow (using the Opera technique, but could easily be converted to the more cross-browser syntax):

    img.beveled
    {
        position:relative;
    }
    img.beveled:before, img.beveled:after
    {
        position:absolute;

        left:-2px;
        top:-1px;
        border:2px solid rgba(100,100,100,0.25);
        border-width:1px 2px 2px 2px;
        display:block;
        content:"0a0";
        width:100%;
        height:100%;
    }
    img.beveled:after
    {
        left:-3px;
        top:-2px;
        padding:2px 2px 1px 2px;
        border-width:1px;
        border-color:rgba(100,100,100,0.1);
    }

    mmm .. dropshadows

  • Brian Allen

    One of those awesome things you can but probably should NOT do. :)

  • Ben Barber

    Technique #3 also works in Opera 10.5 if you add ‘box-shadow’ in addition to firefox’s ‘-moz-box-shadow’.
      box-shadow:-5px -5px 2px rgba(0,0,0,0.4) inset, 5px 5px 2px rgba(255,255,255,0.4) inset;

  • commonsense

    Oh for God’s sake – just use photoshop. What is it with code junkies? Are you averse to doing things simply????

  • Poeplipoe

    Why on earth would you want to add bevel to an image?
    Why not explain how to have blinking text with CSS :-p

  • http://www.brothercake.com/ brothercake

    There are any number of reasons why it’s better to do things like in CSS rather than by processing the image.

    What if I wanted to use the same image elsewhere, without the effect — I’d have to have two copies. What if I had hundreds of images that I wanted this effect on — I’d have to make hundreds of copies; and what if wanted to change slightly later — I’d have to edit them all again, instead of just changing one line of code.

    It is *always* better to keep data in as raw a form as possible, and apply design effects retrospectively. Why do you think we have markup languages? Or to analogise, why do sound engineers record instruments without effects and then apply them afterwards – why not just record things with the effects already applied?

    It’s because retrospection gives you the opportunity to make changes, ad-infinitum, without having to go back to the raw data. And because it leaves the raw data in a form that easier and quicker to work with, because there’s less noise in the signal-to-noise ratio. All told, this is the simplest way.

    @Ben Barber – Nice one :) Ironic that only the other week I was preaching to someone the value of using the standard syntax as well as the vendor syntax to provide forward compatibility, and here I am not doing it! I’ll update the demo with it; may as well add the webkit syntax as well, in case a new version comes along with inset support.

  • Justen

    The box shadow inset example works fine in Chromium, and thus I assume, webkit browsers et-al.

  • ricmo

    “It is *always* better to keep data in as raw a form as possible, and apply design effects retrospectively.”

    I think you’re confusing HTML/CSS with images here. With images, the “as raw as possible” form would be a PSD, TIFF or camera raw file and we use style guides and workflows to create consistent effects. Every professional web designer knows that we just don’t simply scale down a large image using code to create a thumbnail in a web page. We use Photoshop to resize it and create an optimised thumbnail sized image. That’s right. We end up with two versions of the same image on the web server god forbid! And that’s the way it’s supposed to be done.

  • http://www.brothercake.com/ brothercake

    @ricmo – it really depends on the circumstances. We resize a larger image to create a thumbnail because that affords properties which can’t be acheived any other way – the file is smaller, and the anti-aliasing makes it look better. But if that were not the case – if it were possible to embed the same image many times in different forms from a single master image, without any consequent disadvantages, then obviously that’s what we’d do.

    If it’s possible to create a particular effect without modifying the original image, then how can that be anything but a good thing? Arguably the effect being created in this example could be done better in photoshop, and if so fair enough. But that doesn’t detract from the fact that this is a useful technique with multiple potential applications, as the dropshadow variation I posted hopefully shows.

    It’s not a competition. It’s just throwing ideas around.