When building style guides and front-end frameworks buttons are usually some of the first elements to be designed. They seem simple but they offer a glimpse of what the whole user interface will be like. From using a color palette to setting up the different visual states in this article I’m going to build all the reusable CSS styles to build buttons.

Buttons in HTML

It’s useful to have the possibility to apply the design of buttons on link elements, button and input tags with the same button class.

<a href="…" class="button">Link</a>

<button type="button" class="button">Button</button>  
<button type="submit" class="button">Button submit</button>

<input type="button" class="button" value="Input button"/>  
<input type="submit" class="button" value="Input submit"/>  

Without any CSS styling applied this is a screenshot of what we get on Chrome on Windows 10. Starting from here it’s time to make a real design!

Buttons in CSS

For this article I’m implementing all the styles in pure CSS without the Sass preprocessor. To do so we can leverage the layers of how an HTML element is drawn in CSS. From the lowest layer to the nearest:

  1. Outer shadow with box-shadow.
  2. Base flat color with background-color.
  3. Transparency gradient with background-image.
  4. Darker border on top of the background with border.
  5. Inner shadow with box-shadow and the inset keyword.

The golden rule is to add only very subtle effects. In this article I’ll add them step by step and hopefully I won’t overdo them!

Flat color, size and text

The first step is to start with a flat background and apply margin, padding and text styles. And rounded angles also.

.button {
    display: inline-block;
    box-sizing: border-box;
    margin: 0 .5em .5em 0;
    padding: .5em 1em;
    border-radius: .15em;
    background-color: #1098ad;
    color: white;
    text-decoration: none;
    font-family: inherit;
    font-size: inherit;
    line-height: 1;
}

I’m using the em unit for the spacing properties and rounded border. This value is relative to the current font size. So if the font size is increased these properties will be automatically adapted accordingly.

Transparency gradient

In CSS a gradient is as treated as a vector background image dynamically generated by the browser. A background image is always displayed on top of the background color. For a 3D effect I’m adding a vertical transparency gradient from 20% white on the top of the button to 0% white (fully transparent) to the bottom.

.button {
    background-image: linear-gradient(rgba(255, 255, 255, .2), rgba(255, 255, 255, 0));
}

With the transparency gradient we can later change the background color without even modifying the gradient to keep the same effect. This is what I’ll use later in this article for the alternative color button style.

Border

In CSS a border is displayed by default on top of the background. A semi-transparent black gives a shade of the base color I’ve chosen.

.button {
    border: .075rem solid rgba(0, 0, 0, .1);
}

The rem unit is the root font size of the document. With this unit it’s possible to have a more precise control on High-DPI (Retina) screens than with the pixels px unit. Here this value is trial and error.

In the case we would like to choose whether the background is displayed below the border or not we could rely on the background-clip property. Here’s the detailed documentation page on MDN about it.

Outer shadow

For a more visible 3D effect I’m adding a drop shadow below the element. Like for the border values I’m using the root rem font size unit.

.button {
    box-shadow: 0 .075rem .1rem rgba(0, 0, 0, .15);
}

Inner shadow

In CSS the drop-shadow property accepts a list so I’m adding a second shadow effect. This effect now has the inset keyword to display it inside the element.

.button {
    box-shadow:
        0 .075rem .1rem rgba(0, 0, 0, .15),
        inset 0 .075rem rgba(255, 255, 255, .3);
}

This is a simplified syntax of the box-shadow property; we could apply any number of shadow effects on a single element:

.element {
    box-shadow:
        offset-x offset-y blur-radius [spread-radius] color,
        inset offset-x offset-y [blur-radius] [spread-radius] color;
}

The full syntax is here on MDN.

Full code of the base class

This is the whole code of this slightly skeuomorphic button. Of course if I wanted to follow the strict flat design language I could simplify or even remove a few properties like the gradients and shadows. This is just an example made to be tweaked and even improved!

.button {
    display: inline-block;
    box-sizing: border-box;
    margin: 0 .5em .5em 0;
    padding: .5em 1em;
    border: .075rem solid rgba(0, 0, 0, .1);
    border-radius: .15em;
    background-color: #1098ad;
    background-image: linear-gradient(rgba(255, 255, 255, .2), rgba(255, 255, 255, 0));
    color: white;
    text-decoration: none;
    font-family: inherit;
    font-size: inherit;
    line-height: 1;
    box-shadow:
        0 .075rem .1rem rgba(0, 0, 0, .15),
        inset 0 .075rem rgba(255, 255, 255, .3);
}

States

Now that I’ve defined the styles for the base of the button I’m going to handle several states to add the visual feedback relative to pointer and focus events.

Pointer over

This is the :hover pseudo-selector in CSS. I’m tweaking the gradient and border to slightly darker values for more contrast. This adds a visual feedback as an actionable element when hovering with the mouse.

.button:hover {
    border-color: rgba(0, 0, 0, .5);
    background-image: linear-gradient(rgba(255, 255, 255, .1), rgba(0, 0, 0, .1));
}

Focused

When navigating with the keyboard through Tab navigation the :focus pseudo-selector is activated when the element is in focus. Of course we may keep the default focus styles of the operating system and browser but here I’m customizing the focus styles to show what’s possible. This is just an example and not mandatory. And in this case this dotted focus style may be used on other elements and not just buttons. Anyway this is a good idea to basically give the same styles on pointer over and focus states.

.button:hover,
.button:focus {
    border-color: rgba(0, 0, 0, .5);
    background-image: linear-gradient(rgba(255, 255, 255, .1), rgba(0, 0, 0, .1));
}

.button:focus {
    outline: .075rem dotted #bbb;
    outline-offset: .2rem;
}

Pressed

The pressed visual state is enabled with the :active pseudo-selector. Here I’m inverting the gradient direction from a darker value on the top to a lighter value on the bottom. The active state is important since it gives the user the visual feedback that the button is activated and hopefully does something! I’m also removing the outer shadow and instead adding a dark inner shadow for a pressed effect.

.button:active {
    background-image: linear-gradient(rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
    box-shadow:
        inset 0 .075rem .1rem rgba(0, 0, 0, .2);
}

Disabled

I’m applying the styles for disabled controls through the HTML disabled attribute available on form elements like <button> and <input> or the disabled class on link <a> elements. On link elements the disabled attribute doesn’t exist in HTML.

<a href="…" class="button disabled">Button</a>  
<button class="button" disabled>Button</button>  

Implementing a disabled state with a decreased opacity is a quick win. I’m also disabling events like click, hover and active by setting the CSS pointer-events property to none. With this value the events aren’t even triggered in JavaScript. This is a quick win again!

.button.disabled,
.button[disabled] {
    opacity: .5;
    pointer-events: none;
}

Alternative button styles

I’ve defined a base class where some of its properties can be overridden to make alternative styles. I’m going to add a few additional classes to display outlined (bordered) buttons, round (circular) buttons and another class to have an alternative color. These classes will override the main button class and its visual states when necessary.

Outline

I’m designing a simplified button with just a border and without any background.

<a href="…" class="button button-outline">Button outline</a>  
.button-outline {
    background-color: transparent;
    background-image: none;
    border-color: #1098ad;
    color: #1098ad;
    box-shadow: none;
}

.button-outline:hover,
.button-outline:focus {
    background-color: #1098ad;
    background-image: none;
    color: white;
}

Round

This circular button style can be used for pagination numbers for example. Or the button could contain an icon.

<a href="…" class="button button-round">99</a>  
<a href="…" class="button button-outline button-round">99</a>  
.button-round {
    width: 2.15em;
    height: 2.15em;
    padding-left: 0;
    padding-right: 0;
    border-radius: 50%;
    text-align: center;
}

Text

This text button is quite useful to have proper alignments and sizes between real buttons and this simplified text button or link.

<a href="…" class="button button-text">Button text</a>  
<button class="button button-text">Button text</button>  
.button-text {
    padding-left: 0;
    padding-right: 0;
    border: transparent;
    background-color: transparent;
    background-image: transparent;
    box-shadow: none;
    color: #1098ad;
}

.button-text:hover,
.button-text:focus {
    background: none;
    text-decoration: underline;
}

Alternative color

I don’t have much to override to add an alternative color since the same gradients and shadows are used. I can use this new button-alt class on all the previous types of buttons.

<a href="…" class="button button-alt">Button</a>  
<a href="…" class="button button-outline button-alt">Button outline</a>  
<a href="…" class="button button-round button-alt">99</a>  
<a href="…" class="button button-text button-alt">Button text</a>  

This alternative color can be applied on all the button states and styles I’ve already designed. This gives a lot of combinations but fortunately not that much CSS properties to write.

.button-alt:not(.button-outline):not(.button-text) {
    background-color: #c2255c;
}

.button-alt.button-outline {
    border-color: #c2255c;
    color: #c2255c;
}

.button-alt.button-outline:hover,
.button-alt.button-outline:focus {
    border-color: rgba(0, 0, 0, .5);
    background-color: #c2255c;
    color: white;
}

.button-alt.button-text {
    color: #c2255c;
}

Demo

This is the full interactive demo with all the states and styles I’ve designed for this article. Feel free to fork this code and make your own design! Of course I could add a few improvements like transition and animation effects. And an animated loading state that could be used while performing a JavaScript action like an Ajax request. For another article, why not!

See the Pen Designing Buttons in CSS by Frederic Perrin (@blustemy) on CodePen.