Grow your CSS skills. Land your dream job.

Crafting Minimal Circular 3D Buttons with CSS

Published by Guest Author

The following post is written by Brandon Pierce. Brandon saw some nicely designed buttons by Wouter on Dribbble and set about building them with CSS. They came out nicely and he agreed to post about the process here.

Here's what we are going to make in this tutorial:

View Demo   Download Files

The Base HTML

We will be using a simple unordered list1 within a <nav> tag to create our buttons.

  <ul class="nav">
    <li><a href="#" class="icon-home"></a></li>

    <li><a href="#" class="icon-cog"></a></li>

    <li><a href="#" class="icon-cw"></a></li>

    <li><a href="#" class="icon-location"></a></li>



Some Resources

  • These icons in the buttons come from the Fontello icon font and web app.
  • The textured background is from Subtle Patterns.

CSS for the Icon Font

There are a few ways to go about this, but we'll go with this:

@font-face {
  font-family: 'fontello';
  src: url('../fonts/fontello.eot');
  src: url('../fonts/fontello.eot?#iefix') format('embedded-opentype'),
       url('../fonts/fontello.woff') format('woff'),
       url('../fonts/fontello.ttf') format('truetype'),
       url('../fonts/fontello.svg#fontello') format('svg');
  font-weight: normal; font-style: normal;

[class*=" icon-"] {
  font-family: 'fontello';
  font-style: normal;
  font-size: 3em;
  speak: none;

.icon-home:after     { content: "\2302"; } 
.icon-cog:after      { content: "\2699"; } 
.icon-cw:after       { content: "\27f3"; } 
.icon-location:after { content: "\e724"; }

CSS for the Buttons

The buttons will be arranged horizontally by making the list items inline-block. We'll make them butt up against each other with a little negative margin (or see these other techniques).

.nav {
  list-style: none;
  text-align: center;

.nav li {
  position: relative;
  display: inline-block;
  margin-right: -4px;

The lovely three dimensionality of the buttons comes from a subtle gradient and bevel and embossing courtesy of a pair of box-shadows, one normal and one inset. The width and height of the buttons are identical creating a square, which turns to a circle with border-radius. The line-height matches the height and text-align is set to center, so the icons will be both horizontally and vertically centered.

.nav a {
  display: block;
  background-color: #f7f7f7;
  background-image: -webkit-gradient(linear, left top, left bottom, from(#f7f7f7), to(#e7e7e7));
  background-image: -webkit-linear-gradient(top, #f7f7f7, #e7e7e7); 
  background-image: -moz-linear-gradient(top, #f7f7f7, #e7e7e7); 
  background-image: -ms-linear-gradient(top, #f7f7f7, #e7e7e7); 
  background-image: -o-linear-gradient(top, #f7f7f7, #e7e7e7); 
  color: #a7a7a7;
  margin: 36px;
  width: 144px;
  height: 144px;
  position: relative;
  text-align: center;
  line-height: 144px;
  border-radius: 50%;
  box-shadow: 0px 3px 8px #aaa, inset 0px 2px 3px #fff;

We can flatten out the buttons a bit on :hover to create a pressed look. You may want to save that for :active and do something else for :hover, but we'll leave that up to you.

.nav a:hover{
  text-decoration: none;
  color: #555;
  background: #f5f5f5;

At this point our buttons should look like this:

Not bad, but we have a ways to go.

Now on to the fun part, were going to be using pseudo elements to give us some additional elements to work with to create the rest of the effects. To create the horizontal "groove" you see between each button were going to add the pseudo selector :before to our list items. Using negative z-index we can keep it beneath everything. The double border give it the inset look. The negative margins we used on the list items earlier are what allow this groove to look continuous. We take advantage of absolute positioning to center the line.

.nav li:before {
  content: "";
  display: block;
  border-top: 1px solid #ddd;
  border-bottom: 1px solid #fff;
  width: 100%;
  height: 1px;
  position: absolute;
  top: 50%;
  z-index: -1;

For that extra three-dimensionality, we'll create a "well" around each anchor that the button sit inside. We can create this again with no additional HTML by using a pseudo element.

.nav a:before {
  content: "";
  display: block;
  background: #fff;
  border-top: 2px solid #ddd;
  position: absolute;
  top: -18px;
  left: -18px;
  bottom: -18px;
  right: -18px;
  z-index: -1;
  border-radius: 50%;
  box-shadow: inset 0px 8px 48px #ddd;

The size of the well (a bit larger than the button itself) comes from absolutely positioning itself behind the button and stretching itself out by using negative positioning values (top/bottom/left/right). The 3D look this time comes from a top border and inset box-shadow creating an internal shadow.

And we're done!

That wasn't so bad was it?

View Demo   Download Files

Browser Support

These buttons use a good bit of CSS3 and some pseudo elements which are CSS 2.1. Nothing too overly progressive though. Any version in the last few years of Safari, Chrome, Opera, or Firefox will have no trouble. IE 8 doesn't do CSS3, so it falls back to this:

Not a big deal.

IE 7 is similar only without the "groove" line as it doesn't do pseudo elements. Also not a big deal.


1 Unordered lists may not actually be ideal for navigation. We still used them here because we needed the extra element.


  1. Polly
    Permalink to comment#

    Very smart and beautiful buttons!

  2. I’ve been making 3D buttons like that for a while, but that well just makes it that much better!

  3. Peter
    Permalink to comment#

    Totally dig it…. how would we go about and make these responsive? Can we go percentage sizes? Or is it not even worth it… I ‘hear’ a fiddle coming on….

  4. Smooth… but I just wanna press them! :)

    I wonder why the active state is missing, I think it would be a great addition.

  5. Richard Capello
    Permalink to comment#

    Fontello is a great piece of open-source software. :)

    • Re: Fontello. Wish I could get access to the their website. Not loading right now (June 13/9am): Just glad I’m not a time sensitive project right about now.

  6. Erwann31
    Permalink to comment#

    Nice, but not accessible !
    Not for crawlers, nor for users who have deactivated CSS,
    who use a text browser or a screen reader.

    • Sunny
      Permalink to comment#

      Agreed! Empty a tags are horrible for usability and SEO. Put link text inside a span which has something like the HTML5 boilerplate’s visuallyhidden class to maintain both accessibility and visual appeal.

    • planet71
      Permalink to comment#

      Well, you can still insert title for “a” element ro nomral text between and and just set text-indent: -9999px or whatever. I see no problem for seo. This buttons are as good as buttons with images.

  7. These are great! But they are SORELY missing the Active state!!!!

  8. bill
    Permalink to comment#

    In my IE8 not only does the effect not come through, the glyphs are different!

    • Bill,
      Chris has already mentioned in his article that the browser support doesn’t work in IE8, however there are ways around it, just use a separate stylesheet for anything under IE9. Honestly you should really be upgrading to IE9 anyway, or just using a better browser full stop, like: safari or chrome.

    • Tommy
      Permalink to comment#

      Honestly you should really be upgrading to IE9 anyway, or just using a better browser full stop, like: safari or chrome.

      Come on, did you stop to think that perhaps Bill is already using a perfectly capable browser? He is, after all on this website to begin with. I would think it safe to assume he might be keeping his clients/customers in mind when testing older browsers.

      It reminds me of an incident that happened last weekend at the FrontEnd design conference in St. Pete; a speaker who specialized more in “bleeding edge” browser usage was asked what he suggested to devs/designers who needed to support older browsers. His answer was to suggest that those users upgrade. Not very practical for an e-commerce site (where I want them to convert and could care less what browser they use), or most other websites that want to cater to a large audience. Sadly, some folks seem to forget we are building this “web” for more then just designers/devs, we also have to keep others in mind who might not spend 8+ hours a day behind a computer.

    • Tommy,
      I see where you are coming from and the point you are making is a good one. Maybe I worded what I was saying wrong, I wasn’t implying that we should bodge our work and hope people just use better browsers. I think that every site needs to be able to work in all browsers, hence why I suggest a separate stylesheet for users in a lower generation of IE.
      The little comment at the bottom of my point was not to say that developers shouldn’t take into account the problems we face with browser support, it was just a suggestion to Bill personally. He probably already does have a perfectly capable browser and it was probably foolish to say he needed to upgrade; however my intention wasn’t to degrade the importance of browser support.

  9. Yea, I was expecting some awesome :active state.

  10. Are you sure about that IE screenshot? The demo buttons don’t seem to render at all in IE7 (testing in Adobe Browser Lab and Skytap virtual machine).

  11. Erwann31
    Permalink to comment#

    It’s normal, IE7 doesn’t support :before and :after pseudo-elements.

  12. I’ve too been using this button style for quite a while, I’d previously been serving them as images from the original Photoshop designs…

    Great post as always Chris. Beautiful work.

  13. I like this, very simple. But for better accessibility with screen readers, wouldn’t it be better to have the text for the navigation there, with a text-indent:-9999? :)

  14. Patrick
    Permalink to comment#

    Will the this work in IE with &lta href=””&gt selectivizr&lt/a&gt?

  15. James
    Permalink to comment#

    I also think it should have some kind of effect on hover – perhaps it can go into a “pressed down” state.

  16. They look great, and they look good on IE9 too, except for the gradient which is not a big deal. They do not look good on older IE versions, but who cares?

  17. DUDE! These are niiiice… Going to see if I can work on the reverse for black buttons! SWAG!

  18. Wow, very impressive, definitely worth using on future sites. The only problem I see is that some of the icons seem to be a little out of place, but that’s something easily fixed. Thanks for sharing.

  19. Permalink to comment#

    Very nice, even if IE7 doesn’t support :before and :after pseudo-elements. Besides, IE7 is just about dead according to the…

    • Try telling real world clients that IE7 is dead. Why would you assume that your client base has a similar browser profile to that of W3schools visitors?

    • If you really need IE 7 support just don’t use :before, put the icon in the markup. Or use some JavaScript (not ideal, IE 7 is already slow) to read the data attribute and put it in the markup.

  20. Permalink to comment#

    I understand how pseudo elements work, but I’ve not used ‘em very much. This is a great demonstration for that. What I would like a tad more explanation about is what the construct and effect of this element is exactly:

    [class*=" icon-"] {...}

    Thanks! :-)

    • amidude
      Permalink to comment#

      I believe that is a CSS3 Attribute Selector used to identify any class “containing” the prefix/attribute ” icon-“.

    • Permalink to comment#

      That confirms my assumption. I mean, that’s what I thought it was doing based on the classes that followed. I love stuff like this that helps reduce coding. I just gotta remember and get used to implementing it. :-)

    • Pat
      Permalink to comment#

      I use this a bit for namespacing components in my css rather than having multiple classes e.g. “icon icon-home”..The “icon-” example you already see here but also for items such as “list-“, “btn-” or “pod-“. etc.

      I set my base styles in the [class*=”btn-“] declaration and then write my overrides or extensions in the subsequent declarations e.g. “btn-primary” .

      There are some considerations with this though. You have to make sure no other classes contain the keyword you use the “*=” selector on. Also the “*=” selector is probably one of the least performant of the CSS selectors. But in fairness if you are worried about performance you are probably better off focusing your efforts elsewhere, e.g. gzip, minifying or optimising images, as tweaking CSS selectors is more micro-optimisation.

    • Troland
      Permalink to comment#

      The pesudo tell you that the elements with the named class began with the icon-

  21. Sukhjinder
    Permalink to comment#

    It is not working in IE 7-8 do we have any fallback code for IE?

  22. Patrick
    Permalink to comment#

    Pseudo elements like :before and :after and even :nth-child(1) will work in IE7 if you use selectivizr.

    • :nth-child, yes, :before/:after, no

    • Patrick
      Permalink to comment#

      Ooops, you’re right. I forgot I changed the layout a bit to adjust.

      BTW, just wanted to say thanks. I watched one of your screen casts a while back where you redid a friend’s site, a trumpet player I think, which got me into WP(before it was all CI). I became a regular visitor to your site, bought your book, watched your vid on a thousand times and now what was a part time gig (which I did more for fun than anything really), is seriously threatening to turn into a full time job. I got four sites in the pipeline right now!

  23. That’s really amazing style based on pure CSS.
    And nowadays, I encounter many cases about IE (IE really sucks, IMHO). It’s not a big deal, lol

  24. DUDE! These are niiiice…

  25. Antonio Lettieri
    Permalink to comment#

    That’s awesome!
    Side note: display:block isn’t necessary when using absolute positioning. Element is no longer part of the page flow.

    Might help save a couple of css lines ;)

  26. Troland
    Permalink to comment#

    Well done, you guy,that i have a question that the code ” .
    nav a:before {
    position: absolute;
    top: -18px;
    left: -18px;
    bottom: -18px;
    right: -18px;
    } It seems the four positoning param make a width and height both of which was calculated by the a element plus the margin for the nav a:before pesudo element.
    So am i right?

  27. Jason Hess
    Permalink to comment#

    I have a question concerning this example code:
    .icon-home:after { content: “\2302″; }
    .icon-cog:after { content: “\2699″; }
    .icon-cw:after { content: “\d7c60″; }
    .icon-location:after { content: “\e724″; }

    I see how the icons are being generated using the specific “{ content: “\2302″; }” (as above). This woud be the icons on the buttons themselves for example.

    How would I replace the icons from; Do they give you the numerical content Id # once downloaded or how does that work?

    I would appreciate your help in this matter.

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".