02UI

Select

A select lets someone choose one option from a list that stays hidden until they open it. That hidden list is the whole trade: less space on the page, one extra step before any option is visible.

Forms and inputBuild difficulty: Medium<select>

Also called Dropdown, Picker, Combo box.

A select spends one click to save a lot of space. Everything else about the component follows from that trade: the list has to be short enough to scan once it opens, and the trigger has to say plainly what's inside before anyone opens it.

Anatomy of a select

Anatomy of a select: the label above it, the trigger, the selected value inside it, the chevron, the open listbox below, and the check mark on the current option.
  1. 1Label. Above the trigger, connected with for and id, same as any other field.
  2. 2Trigger. A button showing the current value or a placeholder. Never blank with nothing said about what it opens.
  3. 3Value. The chosen option's text, or muted placeholder text naming what to choose.
  4. 4Chevron. The only visual sign that a click opens a list rather than performing an action straight away.
  5. 5Listbox. Appears on open, closes on a choice or on Escape, and scrolls internally once the list runs long.
  6. 6Selected mark. A check beside the current option inside the open list, so the choice already made is visible while picking a new one.

The trigger is a button wearing a different label. It shows the current value or a placeholder, never blank and unexplained, and the chevron is the only visual cue that clicking it reveals more rather than performing an action.

Here it is working. Open it and notice the currently selected option carries a check mark, the one detail a plain button never needs.

Open it. The currently selected option carries a check mark on the left, the one detail a plain button never needs.

When to use a select

Use a select when

  • There's a small to medium, known list of options, roughly 5 to 15
  • Exactly one option should be chosen
  • The full list doesn't need to stay visible, and showing it would crowd the page
  • Nobody needs to search or filter to find the right option

Reach for something else when

Variants

Grouped by region, with a leading icon per option, and a compact trigger for a dense row.

Grouped options. A label above each cluster of related options, useful once the list crosses roughly 8 to 10 items and a structure already exists, like time zones grouped by region.

With icons. A small icon ahead of each option's text, for values people recognise visually faster than they read, like a payment method or a status.

Compact. A shorter trigger height for a dense toolbar or table row, where a full-size control on every row would crowd the layout.

States

Placeholder
Filled
Focus
Error
Disabled
Open
Canada
Canada
Mexico
United States
StateWhat changesWatch out for
PlaceholderMuted text names what to choose, nothing is selectedDifferent from a real value. Style it distinctly, usually in text-muted-foreground
FilledThe chosen option's text replaces the placeholderContrast at least as strong as any other filled field
OpenThe listbox appears, the current option carries a check markOnly one select opens at a time. Opening a second should close the first
FocusA visible ring appears on the triggerNever remove it without a visible replacement
ErrorRed border, message below, aria-invalid="true"Same rule as any other field: say what's wrong and what to do
DisabledReduced opacity, not focusable, not submittedState the reason nearby, the same as a disabled checkbox

The placeholder and filled states get confused most often. A select styled so the placeholder looks like a real, if unremarkable, value hides the fact that nobody has chosen anything yet.

How a select behaves

Only one option is ever chosen. The moment two values need to be true at once, the control is a group of checkboxes wearing a select's trigger.

Opening one closes any other. Two open listboxes on the same screen compete for the same keyboard input and the same attention.

Selecting an option closes the panel. A select that stays open after a choice, waiting for a separate confirm step, is behaving like a form the person didn't ask to fill in.

Long lists scroll inside the panel, not the page. A fixed maximum height with scroll buttons at either end keeps the trigger's position on the page stable no matter how long the list underneath it runs.

Type-ahead jumps to a match. Typing the first letters of an option while the list is open (or while the trigger has focus and is closed) should jump straight to it. This is native behaviour in a <select> and worth rebuilding deliberately in anything custom.

Select accessibility

Keyboard

KeyWhat happens
Space or EnterOpens the listbox when the trigger has focus
Arrow down / Arrow upMoves the highlighted option, opening the list if it's closed
Typing a letterJumps to the next option starting with that letter (type-ahead)
EnterChooses the highlighted option and closes the list
EscapeCloses the list without changing the value
TabCloses the list and moves focus to the next control

The rest

  • Follow the ARIA listbox pattern, or start from a native <select> and get it for free. A custom control needs role="combobox" on the trigger and role="listbox" with role="option" on each item, wired through aria-activedescendant or real DOM focus.
  • Label it the same way as any other field. An explicit <label for> connection, never a placeholder acting as the only label.
  • Announce the selected value on open, not just its position in the list. "Canada, selected" tells someone more than "item 3 of 12".
  • Contrast. The trigger's border needs 3:1 against the page at rest (WCAG 1.4.11), same as any other input.
  • Target size. 24 by 24 CSS pixels minimum at Level AA (WCAG 2.5.8). A compact trigger still needs a comfortable hit area around its visible height.

What a select should say

Labels are nouns. "Country" beats "Please select your country".

Placeholder text names the choice, not the action. "Choose a country" beats "Select" or a bare dash.

Option text stays short and scannable. A sentence-length option defeats the entire point of a list meant to be read in a glance.

Sort with a reason. Alphabetical for an open set like countries, logical order for a closed one like shirt sizes. An arbitrary order forces a full read of the list every time.

Group labels are short nouns too. "Americas", not "Countries in the Americas region".

Common select mistakes

Do

An empty placeholder that names the choice. Nobody has picked anything until they actually do.

Don't

Canada was first in the list, not a country anyone here chose. It looks exactly like a real answer.

The one that shows up constantly, and does the most damage, is a select that looks like it already has an answer the moment the page loads.

1. Pre-selecting the first option. Alphabetical or not, whatever sits first in the list ends up looking like a deliberate choice nobody actually made. Start on an empty placeholder unless a specific default is genuinely correct.

2. A long, flat, ungrouped list. Fifty country codes with no grouping and no way to search turns a two-second choice into a scroll-and-squint.

3. A select standing in for two options. "Yes" and "No" hidden behind a click a radio group or a switch would have shown for free.

4. A vague placeholder like "-- Select --". It names nothing. "Choose a shipping method" tells someone what they're about to pick.

5. A hand-rolled listbox built from styled divs. It loses type-ahead, arrow-key navigation and Escape all at once, all of which a native select or a proper ARIA listbox implementation gives for nothing.

6. No visible focus ring on the trigger. The single most common way a select becomes invisible to keyboard use.

How to build a select

<div class="field">
  <label for="country">Country</label>
  <select id="country" name="country" required>
    <option value="" disabled selected>Choose a country</option>
    <option value="ca">Canada</option>
    <option value="mx">Mexico</option>
    <option value="us">United States</option>
  </select>
</div>

The native element gets you the platform's own picker on mobile, full keyboard behaviour, and type-ahead, all with no JavaScript. Reach for a custom listbox, built on the WAI-ARIA Authoring Practices listbox pattern, only once you need something the native element can't render: icons ahead of each option, multi-line option text, or grouped headers with their own visual treatment.

Edge cases

An option's text longer than the trigger. Truncate with an ellipsis and keep the full text available on focus or through the open listbox, never only in a title attribute nobody discovers.

Right to left. The chevron and the listbox's alignment both mirror.

A very long list. Cap the panel's height and let it scroll internally, with the scroll-up and scroll-down affordances a native select gets automatically and a custom one has to rebuild.

Zoom to 200 percent. The trigger, its label and the open listbox all need to stay usable and inside the viewport (WCAG 1.4.4).

Mobile. A native <select> triggers the OS's own picker wheel or full-screen list, which most people find faster to use than a scaled-down desktop-style listbox. A heavily customised control loses this unless it explicitly falls back to something equivalent on touch devices.

Selects in real products

Stripe Checkout renders its country field as a native <select> on mobile so the OS supplies the picker, while desktop gets a custom-styled listbox with the same underlying options.

Linear uses compact selects for priority and status, each option paired with a small icon, which is the model for the "with icons" variant above.

Notion groups property-type options inside a database column's select by category, so a long list of field types stays scannable without needing a search box.

Common questions

Select or radio group?
Ask how much space you have and how many options exist. Fewer than about 5 options that fit visibly on the screen favour a radio group, because every choice is already there without an extra click. More options, or a layout a full list would crowd, favours a select.
How many options is too many for a select?
Past about 15 to 20, scanning one flat list gets slow enough that a search becomes worth the extra UI. That's the point where a combobox, which filters the list as someone types, is the better fit.
Should a select start on a real value or an empty placeholder?
An empty, disabled placeholder ('Choose a country') unless there's a genuinely sensible default, like the country implied by someone's browser locale. Pre-selecting the first item in the list, alphabetically or otherwise, looks like a choice nobody made.
Does a select need a search box?
No, and adding one is what turns it into a combobox. A select's whole value is a short list someone can scan in under a second. Once scanning stops being fast, switch components rather than bolting search onto this one.
Native <select> or a custom built listbox?
Start with the native element. It gets you the OS picker on mobile, full keyboard support and type-ahead for free, with no JavaScript. Reach for a custom listbox, built on the ARIA listbox pattern, only when you need something native selects can't render: icons, multi-line options, or grouped headers with their own styling.

Last reviewed 29 JUL 2026