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.

A selection of <select> styles across Windows XP and OS X, circa 2007
OSBrowserselect
Windows XPFirefoxScreenshot of a select in Firefox for Windows XP, showing the grey chunky old school drop-down
IE 6/7 (XP)Screenshot of a select in IE6 & IE7 for Windows XP in XP mode, showing the colorful, XP-themed drop-down
IE 6 (classic)Screenshot of a select in IE6 for Windows XP in “classic” mode, showing the grey chunky old school drop-down
IE 7 (classic)Screenshot of a select in IE7 for Windows XP in classic mode, showing the grey chunky old school drop-down
OperaScreenshot of a select in Opera for Windows XP, showing the grey chunky old school drop-down
OS XCaminoScreenshot of a select in Camino for OS X, showing the 3D-stylized “Aqua” theme drop-down
FirefoxScreenshot of a select in Firefox for OS X, showing the grey chunky old school drop-down
IE 5Screenshot of a select in IE5 for OS X, showing a muted grey toggle with up and down arrows (a wholly unique design)
OperaScreenshot of a select in Opera for OS X, showing the 3D-stylized “Aqua” theme drop-down
SafariScreenshot of a select in Safari for OS X, showing the 3D-stylized “Aqua” theme drop-down

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.

Summary: <select>
ProsCons
  • compact
  • value and display text can differ
  • supports value grouping
  • options are hidden
  • UI varies
  • CSS styling is limited
  • user-supplied values are challenging

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>
What’s your favorite fruit?

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 (h1h6) 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>
What’s your favorite fruit?

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>
What’s your favorite fruit?

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>
What’s your favorite fruit?

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.

Summary: input[type=radio]
ProsCons
  • all choices are visible
  • choices can be more than just text
  • markup is more complicated
  • too many choices can be problematic
  • user-supplied values are challenging

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.

Interacting with a datalist-backed input in Chrome Canary for macOS. When typing, suggestions are provided and users can pick one using the keyboard or mouse.

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.

Summary: <datalist>
ProsCons
  • compact
  • user-supplied values are easy
  • degrades to a standard <input>
  • can fall back to <input>
  • options are hidden
  • UI varies
  • not obvious that users can pick from pre-defined values
  • users doesn’t need to choose a pre-defined value

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