P

Playing with Code: Creating Segmented Controls with HTML and CSS

• Written by Perceval McElhearn • Short URL: http://prcvl.com/to/3

I recently posted a PSD called Dark-Plastic Segmented Control. It's a pretty simple UI element with a few selected and clicked states:

Download PSD

Using pseudo-classes such as :target, :first-child and :last-child, and a few CSS3 properties, I'll show you how to recreate this UI element in code, using only one image. Here is the final, working result:

View Demo

How Does it Work?

The markup used is pretty easy to understand, as a segmented control is simply a list of links:

<ul class="segmented-control">
    <li id="songs"><a href="#songs">Songs</a></li>
    <li id="albums"><a href="#albums">Albums</a></li>
    <li id="artists"><a href="#artists">Artists</a></li>
    <li id="genres"><a href="#genres">Genres</a></li>
</ul>

Tip: Hover over a code section to toggle syntax coloring.

Each of the segments' state is handled with CSS pseudo-classes:

default none — default declaration is used: .segmented-control li
clicked .segmented-control li:active
selected .segmented-control li:target
selected + clicked .segmented-control li:target:active

As you probably noticed when clicking the segments above, the page jumps down to put the targeted element at the very top of the scrollable view. This is standard browser behavior; if you click a link to an ID within the page, the page will automatically scroll to show it. It works fine if you try it on the demo page since there isn't enough content to fill the browser window.

Understandably, this is not the expected behavior for segmented controls. There are ways to get around this problem, but the code becomes less and less reusable as hacks are added onto it. The goal here is to keep it simple and functional; this is more a proof of concept than a definitive way to switch through content on web pages.

Using the Spec Sheet

In the PSD, I provide a spec sheet describing basic information like the size and color of segments and text labels. This helps setup the segmented control's basic structure:

.segmented-control {
    background-color: #333;
    list-style-type: none;
    width: 283px;
    height: 25px;
    padding: 1px;
}

.segmented-control li {
    background-color: #595959;
    float: left;
    margin-left: 1px;
}

.segmented-control a {
    color: #262626;
    display: block;
    width: 70px;
    height: 25px;
}

The left margin applied to each list item creates the gutter between segments. Thanks to the segmented control's padding, the first segment is already positionned one pixel from the left, so it doesn't need any margin:

.segmented-control li:first-child {margin-left: 0;}

Now, let's further style the text labels in the default state. This really comes down to translating the type settings and layer styles from Photoshop to CSS properties:

.segmented-control a {
    color: #262626;
    font-size: 12px;
    font-weight: bold;
    line-height: 24px;
    text-align: center;
    text-decoration: none;
    text-shadow: 0 1px 0 rgba(255, 255, 255, .15);
    display: block;
    width: 70px;
    height: 25px;
}

Radii, Lighting and Gradients

Our segmented control doesn't quite look like the one in Photoshop yet, so let's add some visual appeal:

.segmented-control {
    background-color: #333;
    width: 283px;
    height: 25px;
    padding: 1px;
    margin: 0 auto;

    -webkit-border-radius: 4px;
    -moz-border-radius: 4px;
    border-radius: 4px;

    -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, .15);
    -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, .15);
    box-shadow: 0 1px 0 rgba(255, 255, 255, .15);
}

To match with the border radius of the whole element, the first and last segments need rounded corners too:

.segmented-control li:first-child {
    margin-left: 0;

    -webkit-border-top-left-radius: 3px;
    -webkit-border-bottom-left-radius: 3px;
    -moz-border-radius-topleft: 3px;
    -moz-border-radius-bottomleft: 3px;
    border-top-left-radius: 3px;
    border-bottom-left-radius: 3px;
}

.segmented-control li:last-child {
    -webkit-border-top-right-radius: 3px;
    -webkit-border-bottom-right-radius: 3px;
    -moz-border-radius-topright: 3px;
    -moz-border-radius-bottomright: 3px;
    border-top-right-radius: 3px;
    border-bottom-right-radius: 3px;
}

To add lighting effects, the Gradient Overlay and Inner Shadow layer styles from Photoshop need to be converted to gradients and inset shadows:

.segmented-control li {
    background-image: -webkit-gradient(linear, left top, left bottom,
                                       from(rgba(255, 255, 255, .1)), to(rgba(255, 255, 255, 0)));

    background-image: -moz-linear-gradient(0 0 -90deg,
                                           rgba(255, 255, 255, .1), rgba(255, 255, 255, 0));

    background-color: #595959;
    float: left;
    margin-left: 1px;

    -webkit-box-shadow: inset 1px 1px 0 rgba(255, 255, 255, .1);
    -moz-box-shadow: inset 1px 1px 0 rgba(255, 255, 255, .1);
    box-shadow: inset 1px 1px 0 rgba(255, 255, 255, .1);
}

Clicked and Selected States

When clicked, a segment appears pressed. To create this illusion, its background color is darkened and inset shadows are added to simulate depression. The text label is also pushed down one pixel:

.segmented-control li:active {
    background-color: #525252;

    -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, .15),
                        inset 0 0 5px rgba(0, 0, 0, .1);
    -moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, .15),
                     inset 0 0 5px rgba(0, 0, 0, .1);
    box-shadow: inset 0 1px 4px rgba(0, 0, 0, .15),
                inset 0 0 5px rgba(0, 0, 0, .1);
}

.segmented-control li:active a {
    text-shadow: 0 1px 0 rgba(255, 255, 255, .1);
    position: relative;
    top: 1px;
}

Segments are selected once clicked. To achieve this, the :target pseudo-class is used. It is usable here because segments are effectively links to themselves. Here is one single segment:

<li id="songs"><a href="#songs">Songs</a></li>

The link is targeting its very own parent element. When the link is clicked, its hash target, in this case #songs, will be appended to the page URL. With CSS, an element's style can be modified after it has been targeted:

.segmented-control li:target {
    background-image:
        -webkit-gradient(linear, left top, left bottom,
                         from(rgba(255, 255, 255, 0)), to(rgba(255, 255, 255, .1))),
        url(images/background-segmented-control-selected.png);

    background-image:
        -moz-linear-gradient(0% 0% -90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, .1)),
        url(images-articles/background-segmented-control-selected.png);

    background-color: #333;

    -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .1),
                        inset 0 0 5px rgba(0, 0, 0, .15);
    -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .1),
                     inset 0 0 5px rgba(0, 0, 0, .15);
     box-shadow: inset 0 1px 3px rgba(0, 0, 0, .1),
                 inset 0 0 5px rgba(0, 0, 0, .15);
}

.segmented-control li:target a {color: #fff; text-shadow: 0 1px 0 #000;}

There also needs to be a style for selected segments which have been clicked; combining pseudo-classes is very easy:

.segmented-control li:target:active {
    background-image:
        -webkit-gradient(linear, left top, left bottom,
                        from(rgba(255, 255, 255, 0)), to(rgba(255, 255, 255, .05))),
        url(images/background-segmented-control-selected.png);

    background-image:
        -moz-linear-gradient(0% 0% -90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, .05)),
        url(images/background-segmented-control-selected.png);

    -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .15),
                        inset 0 0 5px rgba(0, 0, 0, .15);
    -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .15),
                     inset 0 0 5px rgba(0, 0, 0, .15);
    box-shadow: inset 0 1px 3px rgba(0, 0, 0, .15),
                inset 0 0 5px rgba(0, 0, 0, .15);
}

Adding an Alternate Selected Style

The alternate selected style is the same as the default unselected style, except for a darker background color. So we can go back and edit some of the code to also include the alternate style in the original declaration:

.segmented-control li {background-color: #595959; float: left; margin-left: 1px;}

.segmented-control li,
.segmented-control.alt li:target,
.segmented-control.alt li:target:active {
    background-image: -webkit-gradient(linear, left top, left bottom,
                                       from(rgba(255, 255, 255, .1)), to(rgba(255, 255, 255, 0)));

    background-image: -moz-linear-gradient(0 0 -90deg,
                                           rgba(255, 255, 255, .1), rgba(255, 255, 255, 0));
}

.segmented-control li, .segmented-control.alt li:target {
    -webkit-box-shadow: inset 1px 1px 0 rgba(255, 255, 255, .1);
    -moz-box-shadow: inset 1px 1px 0 rgba(255, 255, 255, .1);
    box-shadow: inset 1px 1px 0 rgba(255, 255, 255, .1);
}

Then, adding the new background color:

.segmented-control.alt li:target {background-color: #4e4e4e;}

Incidentally, the alternate selected style looks the same as the default one when clicked, except for the background color:

.segmented-control li:active {background-color: #525252;}

.segmented-control li:active,
.segmented-control.alt li:target:active {
    -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, .15),
                        inset 0 0 5px rgba(0, 0, 0, .1);
    -moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, .15),
                     inset 0 0 5px rgba(0, 0, 0, .1);
    box-shadow: inset 0 1px 4px rgba(0, 0, 0, .15),
                inset 0 0 5px rgba(0, 0, 0, .1);
}

.segmented-control li:active a,
.segmented-control.alt li:active a {
    text-shadow: 0 1px 0 rgba(255, 255, 255, .1);
    position: relative;
    top: 1px;
}

Wrapping Up

Here is the final markup for both styles:

<ul class="segmented-control">
    <li id="songs-final"><a href="#songs-final">Songs</a></li>
    <li id="albums-final"><a href="#albums-final">Albums</a></li>
    <li id="artists-final"><a href="#artists-final">Artists</a></li>
    <li id="genres-final"><a href="#genres-final">Genres</a></li>
</ul>

<ul class="segmented-control alt">
    <li id="songs-final-alt"><a href="#songs-final-alt">Songs</a></li>
    <li id="albums-final-alt"><a href="#albums-final-alt">Albums</a></li>
    <li id="artists-final-alt"><a href="#artists-final-alt">Artists</a></li>
    <li id="genre-finals-alt"><a href="#genres-final-alt">Genres</a></li>
</ul>

And the final result:

Don't forget to view the demo page for a nice showcase and to view all the CSS in one place.

Your Turn

Play with these segmented controls and use them for anything you want! I'd love it if you would to show me anything cool you've done with them.

For more, go back to the homepage.