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.

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.

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.
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.
Pretty nifty!
As far as other suggestions, a simple drag n drop (sortable) solution would also work.
Love it! I’ve always wondered how surveys would do this. Thanks!
Nice! Would be even more useful if instead of unchecking the other item in the column, one would swap them.
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!)
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).
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>
You like Twix over Butterfingers?
a new poll
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
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 theoptions
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. Withselects
, you’d have a 2 column table no matter the number of choices, and your number ofoptions
within eachselect
would be 1 to n, where n being the number of items they are rating. Once someone chooses an item as #1, all other #1soptions
would be disabled/removed, etc.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.
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? :)
I must have watched this video too many times: http://www.youtube.com/watch?v=Zp-PX13ZlX8
Perfect – i’ve just deleted what has taken a day or so to create exactly what you’ve just shown here! frustrating! but thanks
Great stuff! I’m liking it the simple design a lot.
The folks at AVID could’ve used this on their new audio interface shootout.
http://apps.avid.com/mbox-challenge/
very interesting ! good work :)
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.
That’s the entire point =)
Awesome update liked reading every bit of it.
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.
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.
Cool little method. I wonder why someone would need this. I’m eager to hear examples!
Very nice! This will come in handy in a variety of uses. Thanks.
Ran across this and thought I’d take up Rikudo Sennin’s request for a “value-swapping” approach.
My brain was also busy working out an event delegation scenario using MooTools, so, for better or worse, that’s what I used: http://jsfiddle.net/sullenfish/64xWG/
Clever. Feels like “bummer”y when you select something that would normally de-select two others (since it just moves one of them)
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.
you still can only check one radio button in a column. like what if i wanted to rate snickers and twix a 3? i can’t. is there a way?