There can be only one: Options for building “choose one” fields
by Aaron Gustafson published on
When it comes to building out forms, it sometimes seems like there are at once both too few field types and too many. This is especially true when it comes to having users choose an option from a pre-defined list, also known as “choose one” fields.
This article will take you on a three-stop tour of the most common ways to enable this kind of response in your forms, using just HTML. I’ll talk about some of the implications for CSS and JavaScript as well, but this is HTMHell after all, so my main focus is on the markup.
Selection controls (a.k.a., drop-downs)
The <select>
dropdown has been a mainstay of web forms since they first debuted in HTML 2. This relatively simple element allows for an author to define multiple choices for the user to choose from and the whole thing is neatly packaged in a compact control that can be manipulated via the mouse or keyboard or both.
<label for="fruit">What’s your favorite fruit?</label>
<select name="fruit" id="fruit">
<option>Apple</option>
<option>Orange</option>
</select>
By default, the text value of the chosen <option>
is the value of the field. Authors can override this behavior by setting a value
on the <option>
too:
<label for="location">Where are you based?</label>
<select name="location" id="location">
<option value="US">United States</option>
<option value="non-US">Outside the United States</option>
</select>
By default, the first <option>
in the list is used as the default value for the field, which is why you often see <select>
elements begin with an empty value:
<label for="fruit">What’s your favorite fruit?</label>
<select name="fruit" id="fruit">
<option value="">-- Choose One --</option>
<option>Apple</option>
<option>Orange</option>
</select>
Authors can specify a default value by setting the selected
attribute on the appropriate <option>
:
<label for="fruit">What’s your favorite fruit?</label>
<select name="fruit" id="fruit">
<option value="">-- Choose One --</option>
<option>Apple</option>
<option selected>Orange</option>
</select>
Note: If multiple <option>
elements in the same <select>
have a selected
attribute applied to them, the last one in the source order wins.
Though it’s seldom used, <select>
fields also support grouping options using optgroup
:
<label for="fruit">What’s your favorite fruit?</label>
<select name="fruit" id="fruit">
<optgroup label="Pome">
<option>Apple</option>
<option>Pear</option>
</optgroup>
<optgroup label="Citrus">
<option>Orange</option>
<option>Tangerine</option>
</optgroup>
</select>
Another benefit of <select>
is that it’s easy to validate on the client side. All you need to do is slap a required
attribute on there and modern browsers will ensure a value is chosen before the form can be submitted. Don’t forget to use aria-required="true"
to ensure that requirement is exposed to assistive technology as well.
<label for="fruit">What’s your favorite fruit?</label>
<select name="fruit" id="fruit"
required aria-required="true">
<option value="">-- Choose One --</option>
<option>Apple</option>
<option selected>Orange</option>
</select>
One of the major benefits of <select>
is that it’s an extremely compact component, only displaying one choice at a time (provided you aren’t using the multiple
variety that allows multi-selection). This can preserve a lot of screen real estate. That minimal footprint comes at a price, however, as it requires users to interact with the field to see all of the choices. Additionally, extremely long lists of choices will require users scroll the dropdown to see everything.
To speed up navigation in the list of choices, <select>
elements do support keyboard-based movement. Users can use up and down to move around in the list. They can also jump around in the list by typing (a.k.a., "type ahead"). For example, if a user were filling out the field above and typed "o," the value would become "orange." If they opened the drop-down before typing, focus would move to "orange" but the value would not be set unless "orange" was chosen (using enter or space).
It’s worth noting that the matching algorithm for "type ahead" is limited. First, the string match needs to be with the start of the <option>
’s display text. Second, only the display text is considered, meaning it will not match the value
if it differs from what is shown to users.
Another limitation of the <select>
is its style-ability (or lack thereof). In the past this was a really big deal because browsers rendered <select>
drop-downs wildly differently and many of them were highly stylized to match the host OS.
OS | Browser | select |
---|---|---|
Windows XP | Firefox | |
IE 6/7 (XP) | ||
IE 6 (classic) | ||
IE 7 (classic) | ||
Opera | ||
OS X | Camino | |
Firefox | ||
IE 5 | ||
Opera | ||
Safari |
It’s unsurprising that dozens upon dozens of <select>
replacements arose during this period, most of which were highly inaccessible. I worked on one for ages—and wrote a lengthy book chapter about the experience. In the end, I managed to replicate most of the core functionality of a true <select>
(including optgroup
support, keyboard accessibility and more… all progressively enhanced) but it required a lot of extra code and was incredibly fragile (as most client-side JavaScript-dependent interfaces are). I never ended up deploying my <select>
replacement because the extra download cost just didn’t seem worthwhile just to get a drop-down that looked the way I wanted it to.
Thankfully, browsers have largely toned down the design of their <select>
drop-downs, making them blend more seamlessly into our websites. We’ve also been granted a bit more control when it comes to styling <select>
elements, especially when we apply appearance: none
.
The appearance
property governs how an element is rendered. In the case of <select>
, setting it to "none" removes the default UI bits that makes a <select>
look like a <select>
. If you go this route, you have a lot more control over the look and feel of the element, but you also need to add in UI affordances like the arrow indicator, which typically requires you to inject additional markup. Depending on how important it is for you to control the look of a <select>
, you may find it worth doing. I generally don’t.
It’s worth noting that the Open UI team is actively working on a successor to <select>
called selectmenu
. It works largely the same as a <select>
, but it’s highly configurable. Its design can also be fully controlled via CSS. The selectmenu
component is currently available as an experimental feature in Chromium-based browsers.
Though it’s a bit of an edge case, the final challenge presented by the <select>
elements is support for a user-defined value. While not impossible to achieve, it is a little more verbose, from a markup standpoint:
<label for="fruit">What’s your favorite fruit?</label>
<select name="fruit" id="fruit">
<option>Apple</option>
<option>Orange</option>
<option>Other</option>
</select>
<label for="fruit-other">If you chose "other," what <em>is</em> your favorite fruit?</label>
<input id="fruit-other" name="fruit">
Using the above markup, you need to ensure your form processing logic overrides the initial "fruit" value with the user-supplied one. Most back-end languages do this automatically, but if you’re processing the data on the client side it’s something to be aware of. Alternately, you could give the text <input>
a unique name
(e.g., "fruit_other") and check for "fruit" to equal "Other" before replacing that value with the "fruit_other" value.
Client-side validation of this construct is also a bit more involved. It requires that you use JavaScript to dynamically update the "other" field’s required
(and aria-required
) status, based the user choice from the <select>
dropdown.
There is a whole lot more we could explore when it comes to <select>
, but this is a good introduction to the mechanics of the element.
Pros | Cons |
---|---|
|
|
Radio controls
As with <select>
, radio controls (input[type=radio]
) have been a part of HTML forms since they debuted. In order to be accessible, radio controls require a bit more markup as each <input>
requires a <label>
. Here is a minimalist example:
<fieldset>
<legend>What’s your favorite fruit?</legend>
<label>
<input type="radio" name="fruit" value="Apple">
Apple
</label>
<label>
<input type="radio" name="fruit" value="Orange">
Orange
</label>
</fieldset>
Here’s what’s going on in this code block:
- The
fieldset
is binding the whole construct together as a group - The
legend
provides the group’s label. - Each radio control (
input[type=radio]
) is ensconced in a<label>
to ensure it reads properly for accessibility.
I also generally like to wrap the choices within a list (typically a ul
, unless the order matters) because I like how it looks when styles aren’t applied. You could also opt for a div
or p
or nothing at all (as I did in the above example). If you don’t use a wrapper, just make sure your <label>
elements are set to display: block
so the choices don’t run into one another.
You might be wondering why I chose to wrap the <label>
elements around the radio <input>
controls. This is something I’ve done for a long time because it creates an implicit <label>
association with the <input>
.1 It also allows me to write a really succinct selector to differentiate <label>
elements used with radio controls (and checkboxes), which wrap the <input>
, from those used for labeling other <input>
types, <select>
, and textarea
, which don’t wrap the field.
1: If you still have users on really old browsers (think IE7), you should double up with explicit association (using the for
attribute as an id
reference to the <input>
) too as those older browsers require it.
label {
font-weight: bold;
display: block;
}
label:has(input) {
font-weight: normal;
}
When folks talk about using <label>
, they frequently focus on how crucial they are for the accessibility of your forms. That’s absolutely true and you need them for that. What isn’t often discussed is that <label>
elements have a hidden superpower: When you click or tap a <label>
associated with a radio control, the <input>
will be selected.1 That means using a paper label actually increases the hit target for the <input>
, which is a usability win! In fact, proper labeling makes the age-old complaint—that radio controls don’t have large enough hit targets to be mobile friendly—entirely moot.
2: When you click a <label>
associated with another field type (e.g., <select>
, input[type=text]
), the field will receive focus. When you click a <label>
associated with a checkbox <input>
, it will toggle selection of the <input>
.
Radio controls are perfect for when you want users to be able to more easily scan the available choices. Given that every choice is on display, however, they become less useful the more choices on offer. As a general rule, UX folks suggest limiting radio controls to no more than 5 choices, but there are exceptions to every rule. The most important thing to consider is whether users need to be able to read and evaluate the choices against one another. You should also do everything you can to ensure the choices are clear, concise, and make sense to your intended audience.
Another benefit to using radio controls is that the <label>
can be more than just text. There are limits, however. Since a <label>
is interactive (see above), you can’t nest other interactive elements (e.g., a
, button
, audio
, video
) within one. It’s also recommended that you avoid putting headings (h1
–h6
) inside a label as it can cause navigational issues for users of assistive technologies. You can, however, include other non-interactive phrasing elements, including images. That also means you can style the contents of a radio control <label>
pretty much any way you like, provided its functionality is still obvious.
To establish a default value
for the radio group, apply a checked
attribute to the associated <input>
. As with <option>
lists, the last radio <input>
in the group that’s checked
will be the default value of the field.
<fieldset>
<legend>What’s your favorite fruit?</legend>
<label>
<input type="radio" name="fruit" value="Apple">
Apple
</label>
<label>
<input type="radio" name="fruit" value="Orange"
checked>
Orange
</label>
</fieldset>
Radio controls can be designated as required too, using the same required
attribute you’d use in a <select>
. You only need to add the attribute to one of the radio controls in the group to prevent the form from being submitted until the user has chosen one of the options. If you are working on a form where the choices change often, you might consider adding the required
attribute to each of the <input>
elements in order to ensure it doesn’t disappear if the one <input>
in the group with that attribute gets removed for some reason. There’s no penalty for having the attribute repeated on every <input>
in the group.
<fieldset>
<legend>What’s your favorite fruit?</legend>
<label>
<input type="radio" name="fruit" value="Apple"
required>
Apple
</label>
<label>
<input type="radio" name="fruit" value="Orange"
required>
Orange
</label>
</fieldset>
As with <select>
, supporting user-defined values requires a bit more complexity:
<fieldset>
<legend>What’s your favorite fruit?</legend>
<label>
<input type="radio" name="fruit" value="Apple">
Apple
</label>
<label>
<input type="radio" name="fruit" value="Orange">
Orange
</label>
<label>
<input type="radio" name="fruit" value="Other">
Other
</label>
<p>
<label for="fruit-other">If you chose "other," what <em>is</em> your favorite fruit?</label>
<input id="fruit-other" name="fruit">
</p>
</fieldset>
All of the same considerations outlined in my discussion of this approach for <select>
apply to user-supplied values in the context of radio controls as well. The JavaScript necessary to toggle required
and aria-required
on the text <input>
, however, may be a little more complicated, depending on how you set up your event listeners.
While a little more complicated to author, radio controls remain a solid choice when creating a "choose one" type field.
Pros | Cons |
---|---|
|
|
Suggested values
The final option for "choose one" type fields is binding an <input>
to a <datalist>
. This option debuted as part of HTML5. The user experience is quite similar to the experience you get typing in your browser’s address bar: As you type, the UI suggests values for you, based on what you’re typing.
The markup for this construct is pretty straightforward:
<label for="fruit">What’s your favorite fruit?</label>
<input name="fruit" id="fruit" list="fruit-options">
<datalist id="fruit-options">
<option>Apple</option>
<option>Orange</option>
</datalist>
First, you create a <datalist>
element, assign it an id
, and fill it with <option>
elements for the choices (making it look a lot like a <select>
). Then you add a list
attribute to the associated <input>
and set its value to the id
of the <datalist>
. It’s worth noting that you can also reference a single <datalist>
from multiple <input>
fields, should you need to.
Note: Unlike in a <select>
, the selected
attribute does nothing when set on an <option>
in a <datalist>
. Use the value
attribute on the <input>
to provide a default value.
As with the <select>
, input[list]
is a very compact choice because it’s just a single <input>
field. Also like <select>
, however, this approach doesn’t make a user’s potential choices very obvious. In fact, you could argue it makes them even less obvious than a <select>
does, on account of the predictive typing behavior. That said, it does borrow some UX goodies from <select>
in that users can make a choice from the "picker" UI using their keyboard or mouse. They also have the option to just type out their response.
That’s one of the real strengths of <datalist>
: It naturally supports user-defined values over and above the <datalist>
suggestions. That’s a double-edged sword, of course, in that there’s no way to force a user to pick only from the choices you provide. You need to use a <select>
if that’s a requirement.
Requiring a <datalist>
field is just as straightforward as it is with the other field types. Add the required
attribute (and aria-required
) to the <input>
and you’re good to go.
As I mentioned, <datalist>
became a part of HTML much more recently than the other two options. Older browsers have no idea what to do when they encounter <datalist>
, so they will ignore it. That’s totally cool because you still end up with a usable text <input>
. Perfect progressive enhancement!
The <datalist>
approach is hands-down the best choice if you want to suggest potential responses, but also want to give users the option of providing a different value if they need to.
Pros | Cons |
---|---|
|
|
Make your choice
Having seen these varying options, which one should you use? As with many things on the web, it depends on the context. If the choice is between a handful of items, a set of radio controls is likely the best option. You’d also want radio controls if the choices need to include something besides text. If the number of choices is substantial, a <select>
or <datalist>
might be the way to go. And if you need to keep things flexible, <datalist>
is likely your best bet.
While it may be frustrating to have to evaluate so many options, it’s also good to have the flexibility these different approaches enable.
I used CodePen to assemble a minimally-styled demo of all of the fields discussed in this article, if you’d like to experiment with them yourself.
About Aaron Gustafson
As would be expected from a former manager of the Web Standards Project, Aaron Gustafson has been working to make the web more equitable for nearly three decades now. He is a Principal Accessibility Innovation Strategist at Microsoft. In a prior role, he worked on the Edge browser team with a focus on Progressive Web Apps and developer-focused user experiences. He penned the seminal book on progressive enhancement, Adaptive Web Design, and has been known to have some opinions, many of which he shares at aaron-gustafson.com.
Website/blog: aaron-gustafson.com