BERG Cloud Buttons

Chris Coyier //

The buttons at BERG Cloud are pretty sweet looking. It's not a hugely big deal, but the way they have created them uses two separate images (not sprited) and two internal spans. In this tutorial we will recreate them with some modern tricks. We'll use no images (more efficient), less markup (more semantic), and function smoother (no javascript controlling states).

Here are the originals we're trying to recreate:

orig-buttons

And our final result on CodePen:

Simple Markup

A link is a link.

<a href="#" class="button blue">
  Find Out<br>More
</a>

In our case we have "blue" class in addition to "button". This kind of simple thing is worth discussing. For one, in the "real world" I'd have simply the class of button be fully functional on it's own. It would be whatever the most common button style on the site is. Then to handle variations, in the past I've added extra classes, like shown above. The second class would handle overrides. I'd probably write them like:

.button.blue {
   /* overrides */
}

But I'm starting to think that's not so great. That button has twice the specificity of the original button and uses a class that has no stand-alone value. Lately I've seen several code bases that use single class names for variations, like "button-blue" being the sole class name and having that do all the work. Perfectly do-able in regular CSS, but extra easy in Sass with @extend. For instance:

.button {
   /* Styles for basic button */
}

.button-blue {
   @extend .button;
   /* overrides */
}

Super clean authoring, non-bloated output, and a single class name of equal specificity as any other button. I'm on board with that.

Holy box-shadow!

The meat of what is going on here is layer upon layer of box-shadow on an inline-block link. If you create a box shadow with 1px of offset in two directions and zero blur, you can create essentially a 1px border off two edges of a box. If you do that again only with 2px of offset in the same directions, you start to form a little diagonal line and give the the box some faux depth. If you do it six times, you have a pretty hefty looking button.

Now if you double it up and alternate colors (left offset gets one shade, bottom offset gets another shade) you can simulate a bit of lighting.

.button {
    box-shadow:
      /* Left Side */      /* Right Side */
      -1px 0px 1px #6fadcb, 0px 1px 1px #54809d,
      -2px 1px 1px #6fadcb, -1px 2px 1px #54809d,
      -3px 2px 1px #6fadcb, -2px 3px 1px #54809d;
      /* etc */
}

Using the same technique, but reversing the direction and starting where you stopped the edges, you can do it with a really soft transparent black to make a literal-looking shadow. Toss an inset shadow on there too with a soft transparent white, and it drives home the illusion that light is hitting the button.

.button {
  box-shadow:
    
    /* Sides */
    -1px 0px 1px #6fadcb, 0px 1px 1px #54809d,
    -2px 1px 1px #6fadcb, -1px 2px 1px #54809d,
    -3px 2px 1px #6fadcb, -2px 3px 1px #54809d,
    /* etc */

    /* Shadow */
    -6px 7px  0 rgba(0, 0, 0, 0.05),
    -5px 8px  0 rgba(0, 0, 0, 0.05),
    -3px 9px  0 rgba(0, 0, 0, 0.04),
    /* etc */
  
    /* Light & Top Edge */
    inset 0 4px 5px -2px rgba(255, 255, 255, 0.5),
    inset 0 1px 0 0 rgba(0, 0, 0, 0.3);

}

I'm sure ya'll can imagine Sass helping out with this in clever ways. Calculating shades, loops, variables for colors, tighter syntax, etc.

More CSS!

The face of the button is a very subtle gradient (again helping with the lighting). No problem there. Probably best to apply the gradient to the base class of button and then override it for variations.

.button {
  /* ...shadows ... */

 background: linear-gradient(#a2d3e9, #7abedf);
}

The text inside the buttons has a bit of shadow to it. Easy cheesy with text-shadow. Just make sure the shadow makes sense for the background. We made two variations of the button and we essentially need to override every bit of coloring on the yellow version since it's dark-on-light instead of light-on-dark.

.button {
  /* ...shadows ... */
  /* ...background ... */

 text-shadow: -2px 2px 0 rgba(0, 0, 0, 0.2);
}

There is of course a bunch of other CSS properties on these buttons that you'd except. Like turning off underlines, text coloring, setting up spacing, and other obvious stuff.

Interaction

In the original, when you click or tap the button JavaScript applies an "active" class which triggers the "pressing" animation. It's a little wanky since the buttons can get stuck in that state. Try clicking the button and mousing away and letting go. The button stays pressed. Solvable if they did things like removing the active class on mouseleave, but alas. We don't have to worry about that, since we can trigger the pressed state on :active, in which we get all the interactivity specifics automatically.

Our interaction will be the same pressing animation in which:

  1. All box-shadow is removed, making button look flat to the surface
  2. Button is moved to position where it looks like the surface is

Pretty easy.

.button {

  /* ... styling stuff ... */

  /* interaction */
  transition: all 0.1s linear;
  transform: translateZ(0);
}
.button:active {
  box-shadow: none;
  transform: translate3d(-6px, 6px, 0);
}

The transition does the bulk of the work. With that applied, the box-shadow will slowly slink away rather than instantly disappear. While that is happening, we'll move the button. We could relatively position the button and nudge it around with top/left values. The problem with that is that:

  1. It confuses the issue of positioning and interactivity. If the button were to be absolutely positioned, it would get weird quick. There is no top: where-its-at-plus-10;
  2. It's not as performant as moving via translate.

The second thing to note is the use of 3D transforms. We're using 3D transform properties but we're not actually doing any 3D transforms. We're doing that because:

  1. It'll kick on the ol' GPU and get smoother performance.
  2. It will crap-up (thin out) the text in the default state of the button, so it's not a suprise when the transition starts.

WebKit has that nasty (I'd call it a bug) where text being transformed/transitioned looks all thin and crappy. I've attempted to deal with this before unsatisfactorily. My friend Koop once summed it up pretty well in saying "You just decide which is crappier." Option A) Having the text crapped-up all the time so that there is no awkward screen flashes and shifts in text weight. Option B) Let it be. The text will look good by default but you'll get flashing and thinning when the button is in transition or gets transformed.

It's rather dramatic on these buttons.

thick
Without transform (thick, normal)
thin
With transform (thin)

In this rare case, I like the thinned out version anyway, so best of both worlds.

The Arrow

In my first attempt at creating these buttons, I created the arrow with two spans. It worked great, but not much of a semantic improvement from the original. Neil Kinnish forked my Pen and make the arrows with pseudo elements, meaning we're back at just using a happy anchor link only. I since updated my Pen with his code. You'd think my mind would go right to pseudo elements since I used to do a talk exclusively on them and I've long touted their amazingness. I know that in WebKit you still can't transition a pseudo element, but I was under the impression you couldn't transform them either. Either I've always been wrong or that's a new-ish ability.

The trick is to use the two freebie elements to create a little black rectangle absolutely positioned on the right of the button (there will always be space over there thanks to padding). One little black rectangle is rotated 45 degrees (via transform) and the other -45 degrees. When they were nested spans, setting a filter: drop-shadow() made the most sense (apply just to top span), but now with pseudo elements box-shadow on both works just as well.

.button:after, .button:before
    position: absolute;
    content: "";
    right: 15px;
    top: 14px;
    width: 6px;
    height: 18px;
    background: white;
    transform: rotate(-45deg);
    display: block;
    z-index: 2;
}
.button:before {
    height: 14px;
    top: 26px;
    right: 16px;
    z-index: 3; /* on top */
    transform: rotate(-137deg); /* hey, it works */
    filter: drop-shadow(0 -2px 0 rgba(0, 0, 0, 0.15)); 
}
.button:After {
   filter: drop-shadow(-2px 0 0 rgba((0, 0, 0, 0.2)); 
} 

Again you'd have to adjust all the coloration for different button variations. But the positioning would be the same.

Done

Here's the final again on CodePen:

Also: OMG Tiny Printer so cute #want.