Home Blog Index
Joseph Petitti —

How to toggle styles on click in pure CSS

Why would I want this?

There are plenty of times in front-end web development where you want to toggle between two different CSS rules when an element is clicked. Maybe you want a custom menu to drop down, or a set of tabs to show different information. Either way, you could do this with some simple JavaScript. Or, you could use a CSS hack to do it without any scripts at all.

The hack

This CSS trick involves an invisible checkbox and an abuse of the general sibling combinator (~). As you know, you can assign specific styles to checkboxes depending on whether or not they are "checked," like so:

input[type=checkbox]#cb1:checked { animation: spin 1s linear infinite; } @keyframes spin { 100% { transform:rotate(360deg); } }

Try it

Note that the "click me" text is a <label> element, so when you click it the checkbox is toggled.

Just styling checkboxes isn't very fun or impressive, but we can use some sneaky selectors to do some more interesting things.

input[type="checkbox"] { display: none; }
Make the checkbox invisible

<label> elements don't actually have to be next to the input they're labelling; they can be anywhere. And if you can click the label to toggle them, you don't need to see the checkbox at all. those checkboxes can be made invisible with the code above.

The last piece of the puzzle is how to style arbitrary content. CSS provides a convenient sibling selector that allows this. The general sibling combinator (~) selects an element that is at the same level as this one, a child of the same parent. Since the checkbox is invisible, we can choose to place it as a sibling to the element we want to style.

Here's an example of one of the many ways to use this trick:

HTML structure

<div id="tabs-parent"> <div class="tab"> <input type="radio" id="radio-1" name="tabs" checked> <label class="tab-heading" for="radio-1"><h4>HTML</h4></label> <div class="tab-content" id="tab-one"> <h3>Text in one</h3> </div> </div> <div class="tab"> <input type="radio" id="radio-2" name="tabs"> <label class="tab-heading" for="radio-2" id="radio-2-label"><h4>CSS</h4></label> <div class="tab-content" id="tab-two"> <h3>Text in two</h3> </div> </div> <div class="tab"> <input type="radio" id="radio-3" name="tabs"> <label class="tab-heading" for="radio-3"><h4>Info</h4></label> <div class="tab-content" id="tab-three"> <h3>Text in three</h3> </div> </div> </div>

Styles used in this example

div#tabs-parent { width: 100%; position: relative; height: 400px; } input[type=radio], div.tab-content { display: none; } div.tab-content { position: absolute; left: 0; top: 33px; background-color: #005377; z-index: 1; padding: 15px; width: 100%; box-sizing: border-box; height: 100%; overflow: auto; } label.tab-heading { position: relative; background-color: #3f2073; text-align: center; cursor: pointer; width: 150px; float: left; z-index: 2; } label.tab-heading h4 { margin: 0; padding: 5px 10px; } label.tab-heading:hover { background-color: #4ea5d9; } label.tab-heading#radio-2-label { border-left: 2px solid #1f0053; border-right: 2px solid #1f0053; } input[type=radio]:checked ~ label { background-color: #005377; } input[type=radio]:checked ~ div.tab-content { display: block; }

Conclusion

This implementation actually uses radio inputs instead of checkboxes, but the main idea is still the same.

Keep in mind that this page uses absolutely no JavaScript. It just goes to show how powerful CSS is, and how much advanced functionality you can achieve without using scripts.

Now, is this good practice? Probably not. It's a convoluted, opaque way of achieving something that could be done with a couple lines of JavaScript. But if you need to support users who have JavaScript disabled, or just enjoy hacking CSS to do things it wasn't meant to do, this is a good way to do it.