Toggle Buttons Without Javascript

The idea of having a set of buttons that are mutually exclusive (when you select one, all others deselect) or "toggle buttons" is not by any means new. This idea has nearly always been accomplished in the past with JavaScript and its libraries/plugins. What about people who run without JavaScript? Why not do it with just CSS?

The first step is to think about what we are trying to do on the most basic level - from a list of buttons, allow the user to select only one. Therefore, it makes sense to use the HTML element designed to do exactly this, the radio input. Not only do radio inputs meet the requirements, they are 100% semantic in this scenario, they don't need slow clunky JavaScript and still function as expected if the user has CSS turned off.

You can checkout the live fiddle for a demo.

The HTML

The HTML is fairly straight forward, we create a form and a set of inputs. The key is to use <input type="radio"> and the same value in the name attribute on each input as this gives us the mutual exclusivity.

We also need to make sure you have a label for each input (which I'm sure you're doing anyway) with a for attribute that corresponds to its input's id attribute. Note, the label must come directly after the input for the effect to work.

Lastly, the label and the input need to sit inside their own container, I have used a div in this example.

<form>
    <div class="mx-button">
        <input type="radio" name="mx" id="button1" checked>
        <label for="button1" unselectable>Button 1</label>
    </div>
    <div class="mx-button">
        <input type="radio" name="mx" id="button2">
        <label for="button2" unselectable>Button 2</label>
    </div>
    <div class="mx-button">
        <input type="radio" name="mx" id="button3">
        <label for="button3" unselectable>Button 3</label>
    </div>
    <div class="mx-button">
        <input type="radio" name="mx" id="button4">
        <label for="button4" unselectable>Button 4</label>
    </div>
</form>

Something thing to note is the unselectable attribute on each label element. This attribute tells the browser not to let the user select the text contained with in this element. Making the labels act the same way button elements would.

The CSS

I have included the basic CSS required to pull off the effect. (Vendor prefixes stripped for brevity.)

[unselectable] {
    -webkit-user-select: none;
    -moz-user-select: none;
    user-select: none;
}
.mx-button input {
    display: none;
}
.mx-button label {
    background: #ccc;
    border: 1px solid #888;
    color: #666;
    padding: 5px 10px;
}
.mx-button label:hover {
    background: #ddd;
}
.mx-button label:active,
.mx-button input:focus + label {
    background: #aaa;
}
.mx-button input:checked + label {
    background: #b4b4b4;
}

First is [unselectable] { ... }. This picks up all elements that we have said we don't want text to be selectable by the user. Since this is a HTML5 attribute, adding this in the CSS lets non-supporting browsers know what to do with it, and older browsers will just ignore.

Second thing to note is the input[type=radio]:checked + label { ... } selector. This selector says "Find any input tag, with type 'radio', that has been checked; then select the label tag directly next to it." This is what allows us to style the selected button and why it is important that the label comes directly after the input element.

Conclusion

This method of creating mutually exclusive, or "toggle", buttons with radio inputs allows for, not only semantic code, but also fast loading and gracefully degrading sites as we don't have to rely on a chunk of JavaScript. This is something I plan to use in the future, as should anyone looking to create a well structured website.

8 Responses

  • Love this!  However, since my site exists already and I didn’t want to change the breadcrumbs to be in reverse order, I made a few tweaks and it works.

    First, on the “li” element, I changed the float from ‘right’ to ‘left’ and added a position: relative (to cause it to be positioned and be effected by a z-index).  Second, I added a few lines as mentioned in the article that inspired you about using the nth-child selector and set a z-index on nth-child(1) to something high and nth-child(2) to something a tad lower, etc.

    ul li:nth-child(1) { z-index: 9990 };
    ul li:nth-child(2) { z-index: 9980 };
    ul li:nth-child(3) { z-index: 9970 };

    For me, in a standard Rails app, I only go 3 deep anyway, but in my test code I went as far as 9.  Finally, you’ll need to remove the -15px margin on the li:last-child.

    Anyway, that should work

  • Thank YOU for figuring this out.  It was exactly what I wanted to do.

  • Hello,

    Love the idea here, it’s very smart indeed.

    The one problem I’m really struggling with is that the width is static.

    Can you think of any way to be able to have the width adjust to the text/url inside?

  • Hi Rob, thanks for the feedback.

    The issue with dynamic width is that the height needs to adjust accordingly to keep the corners at the right angle. I will have a play tonight when I get home and see if I can figure it out for you.

  • Hi Jeremy, thanks for the reply.

    I initially thought it might be a case of figuring out a formula (using JS to auto populate individual a/span fields using the span innerwidth) but soon hit a brick wall as adjusting one field had an irreversible effect on the others.

    Still pondering this..

  • It should be a fairly simple process with JavaScript, though as keeping with the exercise I will try to make a CSS solution.

  • A CSS-only version would be ideal, I look forward to seeing what you come up with!

Leave a Response

Cancel reply
Google+