HTML & CSS
Article

Building a Style Switcher with Pure CSS Using :checked

By Omar Wraikat

A few years ago, web developers were unable to implement and build so many things using CSS only and without relying on JavaScript. But today CSS is mature enough that it is capable of doing powerful things without writing a single line of JavaScript. You might have also read a couple of articles about “CSS only approaches” that demonstrates the power of CSS.

When it comes to CSS-only approaches, we can’t ignore the :checked pseudo-class selector, which I’m going to use in this post. Of course, I am not the first one to write about this technique, and there have been other more extensive discussions regarding using form elements as JavaScript replacements. For example this Adobe article by Louis Lazaris and this great slideshow by Ryan Seddon.

Using :checked

In short, the :checked pseudo-class selector represents (selects) any radio or checkbox element that is checked or selected. The user can change the state of these elements by checking a checkbox or selecting a different value in a set of radio buttons.

Before we dive deeper, take a look at the final demo to get a sense of what we will be building throughout this tutorial:

See the Pen A Style Switcher with Pure CSS using :checked by SitePoint (@SitePoint) on CodePen.

Now let’s get started.

Building the Settings Box

In the demo, you should have noticed the gear icon and how, when clicked, a box with some options appears. Before we go on explaining the HTML and CSS that makes up that box, take a look at the following code:

/* we don't want the input fields to appear, all we need is the labels */
input[type="checkbox"], input[type="radio"] {
  position: absolute;
  visibility: hidden;
}

.settings-box-element {
  z-index: 10;
}

Since we are only interested in showing the labels, the above code is used to hide the checkboxes and radio buttons. Moreover, all the labels have a class of settings-box-element with a z-index property just to make sure the labels will stay on top of any other elements on the page.

Now we can break down the code that makes up the settings box. Let’s start with the gear button. Here is the HTML:

<!-- the gear icon that opens the box when you click on it -->
<input id="settings-btn" class="settings-btn" type="checkbox">
<label for="settings-btn" class="settings-box-element"></label>

And the CSS:

.settings-btn + label {
  position: fixed;
  top: 130px;
  right: 0;
  display: block;
  width: 35px;
  color: #e83737;    
  text-align: center;
  background: #fff;
  cursor: pointer;
}

If you have read an article or two about using form elements as JavaScript replacements, then you should already know that we need to use input and label elements together, so that if one of the two was removed, nothing would work. So we have a checkbox input with an id="settings-btn" and a label with a for attribute that matches the id value. I’ve also added a class of settings-btn to use for our CSS hook.

In the CSS, the label is given a position: fixed declaration with the appropriate direction properties/values (top and right).

Next is the white box that will virtually contain the buttons:

The HTML first:

<!-- the white box that contains the buttons -->
<div class="buttons-wrapper settings-box-element"></div>

And the CSS:

.buttons-wrapper {
  position: fixed;
  top: 130px;
  right: -200px; /* should match element width */
  width: 200px;
  height: 240px;
  background: #fff;
}

The box is a single div element with classes “buttons-wrapper” and “settings-box-element”. As I said earlier, the latter class is mainly used to give the elements a z-index value. The “buttons-wrapper” is used to style the div element. And as you can see, the div was given a width of 200px and a height of 240px to accommodate the 5 buttons you see in the demo. Also, the div is given a position value of fixed and the appropriate right and top properties. The only thing you need to keep in mind here is that the right property should have the same value as the width but in the negative (in order to make it disappear from the viewport).

Lets now take a look at the code for the remaining markup, that is, the 5 buttons. The comments denote the background styles that they control:

<!-- background styles -->
<!-- light background (#eaeaea) -->
<input id="light-layout" class="light-layout" type="radio" name="layout" checked>
<label for="light-layout" class="layout-buttons settings-box-element">
Light Background</label>

<!-- dark background (#494949) -->
<input id="dark-layout" class="dark-layout" type="radio" name="layout">
<label for="dark-layout" class="layout-buttons settings-box-element">
Dark Background
</label>

<!-- image background -->
<input id="image-layout" class="image-layout" type="radio" name="layout">
<label for="image-layout" class="layout-buttons settings-box-element">
Image Background</label>

<!-- pattern background -->    
<input id="pattern-layout" class="pattern-layout" type="radio" name="layout">
<label for="pattern-layout" class="layout-buttons settings-box-element">
Pattern Background</label>

<!-- hide/show content -->
<input id="hide-show-content" class="hide-show-content" type="checkbox">
<label for="hide-show-content" class="layout-buttons settings-box-element">
Hide/Show content</label>

And here is the CSS:

.layout-buttons {
  display: block;
  width: 150px;
  padding: 10px 0;
  text-align: center;
  border: 2px solid black;
  box-sizing: border-box;
  font-size: 0.875em;
  cursor: pointer;
}

.light-layout + label {
  position: fixed;
  top: 140px;
  right: -150px;
}

.dark-layout + label {
  position: fixed;
  top: 185px;
  right: -150px;
}

.image-layout + label {
  position: fixed;
  top: 230px;
  right: -150px;
}

.pattern-layout + label {
  position: fixed;
  top: 275px;
  right: -150px;
}

.hide-show-content + label {
  position: fixed;
  top: 320px;
  right: -150px;
}

Notice that the first checkbox was given the “checked” attribute. That is because we want it to be the one highlighted by default.

Every input field has an id and every label has a for attribute that matches the id for one of the input fields. And as you may or may not know, the secret behind such a technique is the for attribute, because when a label with a for attribute is clicked, the element that is associated with that particular label will be selected/checked and this therefore allows us to use the :checked selector.

All the above labels have a class of “layout-buttons”. This class is used to give the buttons the default and the common styles such as width, padding, borders, etc. The other classes are used to add the unique styles to each. And as you saw for the gear icon and the white box, the position property is used with the value fixed and the appropriate top and right properties. Note that the top value for every label is 45px greater than the one before it; this is to make the buttons stack above each other nicely and without overlaps. Note also that the right property value is the same as the width of the buttons but in negative.

Now take a look at the last part of our CSS code:

input[type="radio"]:checked + label {
  background: #e83737;
  color: #fff;
  border-color: #e83737;
}

The above CSS is used to change the default styles of the label associated with the selected radio button (we have 4 radio buttons). I used the adjacent sibling selector to target every label that is preceded by an input field of type “radio”. So as you can see, the background and border properties were given the value #e83737 (a reddish color) and the color property the value #fff. Nothing really fancy or complex.

The remaining elements in the HTML will be wrapped inside a div:

<div class="main-wrapper">
  <div class="content">
    <h1>Cool stuff with CSS only!</h1>
    <p>Lorem ipsum dolor sit amet...</p>
  </div>
</div>

Notice in the above code that I positioned every element of the settings box independently where I could just wrap them all inside a div or section element and position this one single element, therefore making things simpler. This is done simply because you can’t select a parent element, only a sibling.

So in this case, all the main content is wrapped inside a div with class="main-wrapper". And as you will see later, to be able to change the styles for this div, we will need to select that div by writing something similar to this:

input[type="checkbox"]:checked ~ main-wrapper {
  /* some styles here */
}

Here I’m using the general sibling selector to select the main div, which is the only way to do that.

Clicking the Gear Icon

Clicking the gear icon should make the settings box appear. Here is the code to make that happen:

.settings-btn:checked + label {
  right: 200px; /* match width of .buttons-wrapper */
}

.settings-btn:checked ~ .buttons-wrapper {
  right: 0;
}

.settings-btn:checked ~ .layout-buttons {
  right: 30px;
}

When the user clicks on the gear icon, the checkbox with id="settings-btn" will be selected, and here comes the magic. Once the gear icon is clicked, the following will happen:

  1. Using the adjacent sibling selector (+), the label that comes immediately after that checkbox will be selected and then moved 200 pixels from the right of the viewport.

  2. Using the general sibling selector ~, the elements with classes “buttons-wrapper” and “layout-buttons” will be selected and then moved so that they are 0 pixels and 30 pixels, respectively, from the right of the viewport.

Both the adjacent sibling selector and the general sibling selector are indispensable here as this technique will not work without them.

Changing the background

Let me remind you of the HTML code for the radio buttons:

<!-- background styles -->
<!-- light background (#eaeaea) -->
<input id="light-layout" class="light-layout" type="radio" name="layout" checked>
<label for="light-layout" class="layout-buttons settings-box-element">
Light Background</label>

<!-- Plus 3 other radio buttons/labels... -->

The background that we will be changing is the background of the .main-wrapper element. Here is the CSS:

.light-layout:checked ~ .main-wrapper {
  background: #eaeaea;
}
.dark-layout:checked ~ .main-wrapper {
  background: #494949;
}
.image-layout:checked ~ .main-wrapper {
  background: url(image url) no-repeat center 0 fixed;
}
.pattern-layout:checked ~ .main-wrapper {
  background: url(images/pattern1.png) repeat;
}

You can see that in the HTML we have 4 input elements of type="radio" and 4 labels. When any of the labels is clicked, the input that is associated with that particular label will be selected and therefore matched by the :checked pseudo-class. Then, depending on which label is clicked, one of the above four styles will be applied to the main wrapper.

Hiding/Showing content

For the show/hide element, I’m using a checkbox:

<!-- hide/show content -->
<input id="hide-show-content" class="hide-show-content" type="checkbox">
<label for="hide-show-content" class="layout-buttons settings-box-element">
Hide/Show content</label>

And here is the CSS:

.hide-show-content:checked ~ .main-wrapper .content {
  display: none;
}

In this case, we tell the browser to select the element with class="content" and set it to display: none” when the user clicks on the associated label, therefore selecting the checkbox.

Conclusion

There are many other things you can do using this selector technique and the limit is your own creativity. If this technique is new to you, I hope this article can serve as a starting point for you to experiment with other possibilities.

Below you’ll find the completed demo:

See the Pen A Style Switcher with Pure CSS using :checked by SitePoint (@SitePoint) on CodePen.

  • Chris Perry

    Great piece of work Omar. I’ll look forward to seeing more from you

    • Omar Wraikat

      Thanks Chris. Glad you liked it.

  • phil

    May I suggest another title? ” Building a Style Switcher with Pure CSS. Check. ” ;)

    Great article! I really enjoy pure CSS solutions like those. Makes my eyes sparkle with stars.

    • Omar Wraikat

      I embrace CSS solutions, too.
      Glad you found it useful :)

  • eddie404

    That’s really great! Nice job Omar and thanks for sharing.

    • Omar Wraikat

      You are welcome Eddie :)

  • http://www.adriansandu.com Adrian SANDU

    I like the article very much. It is great as a proof of concept, but impractical for large projects. One would have to write all affected selectors as many times as the available options. I can’t think of any practical such solution that doesn’t use javascript.

    • http://www.nilsbutenschoen.de Nils Torbjörn Butenschön

      Or, you know, you could maybe use a CSS preprocessor to save you from having to repeat it all over.

      • http://www.adriansandu.com Adrian SANDU

        A preprocessor has no effect on the compiled CSS. If you have 100 rules affected by the theme changes, you need to multiply that by the number of themes in the output file. For the switcher to function, you need to load all the styles, from the very beginning. With Javascript you can load the alternate stylesheets on demand, improving the performance and the experience.

        Of course, this doesn’t detract from the value of the article or the concept. I’m just pointing that a practical and efficient solution for a real project would require more tools.

    • Omar Wraikat

      Yes Adrian I somehow agree with you. CSS-only solutions are not always practical and applicable especially for large projects. All I wanted by writing this article is to show developers (especially the beginner ones) what they can do using CSS.

  • John F

    Hi Omar,

    Nice write up. We have a site in production that uses this concept for a cool theme change:

    liaweston.com

    Just find and click on the semi-hidden skull.

    • Omar Wraikat

      Nice one John!
      Just try to make that skull more appealing.

      • John F

        Thanks Omar.
        Maybe we’ll dress it up in a hat :)

        John

    • Jacob Alvarez

      Cool Easter egg!

  • http://www.nilsbutenschoen.de Nils Torbjörn Butenschön

    Nice read, thanks Omar!

    One small thing though, even if it doesn’t matter much: Instead of transitioning on the ‘right’ property of the box containing the buttons I would transition on the ‘transform’ property, such as ‘transform: translateX(100%)’ for the closed state and ‘transform: translateX(0%)’ for the opened state. That should give a smoother transition.

    • Omar Wraikat

      Good suggestion Nils! I haven’t even thought of that.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Front-end, once a week, for free.