Grow your CSS skills. Land your dream job.

Radio Buttons with 2-Way Exclusivity

Published by Chris Coyier

Let's say you were tasked with creating a UI in which users can rate three candy bars from their most to least favorite. A grid of radio buttons isn't a terrible way to go about this.


A (small) grid of radio buttons.

The good news is that radio buttons have natural exclusivity in the name-group they are in, and we can use that to our advantage. The bad news is that we have interlocking groups and there is no way to express that in a functional way through HTML alone.


Only one radio button can be selected in each circled group

Let's craft the HTML in such a way that the horizontal groups share the same name, granting exclusivity automatically. And we'll also add an HTML data-* attribute which signifies the column which we'll use to simulate the vertical exclusivity with JavaScript1.

<table>
<tr>
    <th></th>
    <th>1</th>
    <th>2</th>
    <th>3</th>
</tr>
<tr>
    <td>Twix</td>
    <td><input type="radio" name="row-1" data-col="1"></td>
    <td><input type="radio" name="row-1" data-col="2"></td>
    <td><input type="radio" name="row-1" data-col="3"></td>
</tr>
<tr>
    <td>Snickers</td>
    <td><input type="radio" name="row-2" data-col="1"></td>
    <td><input type="radio" name="row-2" data-col="2"></td>
    <td><input type="radio" name="row-2" data-col="3"></td>
</tr>
<tr>
    <td>Butterfingers</td>
    <td><input type="radio" name="row-3" data-col="1"></td>
    <td><input type="radio" name="row-3" data-col="2"></td>
    <td><input type="radio" name="row-3" data-col="3"></td>
</tr>
</table>

Our incredibly simple design can be accomplished with this CSS:

table {
    border-collapse: collapse;    
}
td, th {
    border: 1px solid #ccc;
    padding: 10px;
}
th:empty {
    border: 0;
}

I wouldn't have even posted that CSS except I think it's a super useful case of the :empty pseudo class selector.

Now to grant the vertical group exclusivity, we'll use a touch of jQuery:

var col, el;

$("input[type=radio]").click(function() {
   el = $(this);
   col = el.data("col");
   $("input[data-col=" + col + "]").prop("checked", false);
   el.prop("checked", true);
});

When a radio button is clicked, the column is determined and all other radio buttons in that column are turned off, then the clicked on is turned back on.

That's all there is to it.

View Demo   Download Files

For the record, I did get this idea from the screencast I linked up earlier in which this idea was briefly mentioned and then discarded.

Also, I'd be interested to hear if people have alternate design pattern ideas for the "rate these three candy bars" thing. That's always fun to think about.


1 You could, instead, apply the data attributes through JavaScript programatically, or even calculate column on the fly as needed. As ever, there are more ways to kill a cat than choking it with butter.

Comments

  1. Permalink to comment#

    Pretty nifty!
    As far as other suggestions, a simple drag n drop (sortable) solution would also work.

  2. John
    Permalink to comment#

    Love it! I’ve always wondered how surveys would do this. Thanks!

  3. Rikudo Sennin
    Permalink to comment#

    Nice! Would be even more useful if instead of unchecking the other item in the column, one would swap them.

  4. Giannhs
    Permalink to comment#

    thanks! very useful article.
    Change the html rel on prettyprint to css, so that this article becomes perfect ;)
    I would perfer a drag-drop list of items, so that the item at the top of the list is more important. I think the radio grid is a bit confusing for the user. But that’s my opinion :P
    (Hi mum!)

  5. Scott
    Permalink to comment#

    Since the 3 ratings are mutually exclusive in your example, there are really only 3 options, not 9 as you are using. Like Corey said, a draggable list seems the most logical.

    You could also have an interface that requires the user to simply click the three options in order from favourite to least favourite (technically this only requires 2 clicks).

  6. Permalink to comment#

    You’re a wildman Coyier.

    I dig the demo, definitely one of those things it’s tough to think through, but fun when you do.

    Do you know how you’d associate labels with multiple inputs? I’ve never really thought about if you can do <label for="id1 id2">thing</label>

  7. cnwtx
    Permalink to comment#

    You like Twix over Butterfingers?

  8. James
    Permalink to comment#

    Thanks! I just learned the HTML5 spec doesn’t require a default radio-group input to be checked.

    HTML5: “If none of the radio buttons in a radio button group are checked when they are inserted into the document, then they will all be initially unchecked in the interface, until such time as one of them is checked (either by the user or by script).” http://www.w3.org/TR/html5/number-state.html#radio-button-state

    HTML4: “At all times, exactly one of the radio buttons in a set is checked. If none of the elements of a set of radio buttons specifies `CHECKED’, then the user agent must check the first radio button of the set initially.” http://www.w3.org/TR/html4/interact/forms.html#radio

  9. Permalink to comment#

    First, the fact that Snickers, the Oh So Most Holy of Chocolates, isn’t number one in your list makes me want to blacklist your site. That’s just bad form, man.

    As the others said, a sortable list would be better for this particular example. I’d also consider using selects (drop-downs) and then disable the options as each value is chosen for a given element. As the number of elements grows, your radio button grid grows too wide too fast; imagine 20×20, or 50×50, etc. With selects, you’d have a 2 column table no matter the number of choices, and your number of options within each select would be 1 to n, where n being the number of items they are rating. Once someone chooses an item as #1, all other #1s options would be disabled/removed, etc.

  10. Andrew
    Permalink to comment#

    While the draggable interface might be better UI, from a JS perspective, if you’d put values into the HTML you could have used that to make things unique, rather than data-col, no? In this case, anyway, it seems like a value attribute and a data-col attribute would be redundant.

  11. Permalink to comment#

    I couldn’t help but laugh at “Butterfingers”.

    :)

    It’s actually called Butterfinger (no “s”!). But it did make me think of Leon Lett.

    Wow, this was a useless comment. I guess I don’t get a star? :)

  12. Permalink to comment#

    Perfect – i’ve just deleted what has taken a day or so to create exactly what you’ve just shown here! frustrating! but thanks

  13. Permalink to comment#

    Great stuff! I’m liking it the simple design a lot.

  14. Permalink to comment#

    The folks at AVID could’ve used this on their new audio interface shootout.

    http://apps.avid.com/mbox-challenge/

  15. Permalink to comment#

    very interesting ! good work :)

  16. This is a great trick but when I try to select the same rating across all rows it deselects the other rows. The only way I can get all three radio buttons to check is if I use different columns for all three selections.

  17. Permalink to comment#

    Awesome update liked reading every bit of it.

  18. Etienne
    Permalink to comment#

    That could also be done without using data attributes pushing the selectors usage a little further.
    Not sure about the perf differences between the two solutions.

    $("input[type=radio]").click(function() {
       el = $(this);
       col = el.closest('tr').find('input[type=radio]').index(el) + 1;
       $("tr").find("td:eq("+col+")").find("input[type=radio]").prop("checked", false);
       el.prop("checked", true);
    });
  19. Honestly not intuitive Chris. I guess there could be better ways to do it to make a better UX. However, it was fun and creative.

  20. Cool little method. I wonder why someone would need this. I’m eager to hear examples!

  21. Permalink to comment#

    Very nice! This will come in handy in a variety of uses. Thanks.

  22. Thanks for sharing this, Chris.

    I would suggest using the onchange-event-listener for the changing of buttons as it is dedicated to do exactly this. This might be a little bit better for a11y though.
    Maybe there’s a drawback that I’m not aware of at the moment…

    I’ve made a damn easy fiddle with your code.

Leave a Comment

Current day month ye@r *

*May or may not contain any actual "CSS" or "Tricks".