Select
A select lets people choose one option from a short, closed list. It shows the chosen value and submits it with the form.
<tk-field label="Department" helper="Shown on your profile">
<tk-select name="department" placeholder="Select a department">
<option>Design</option>
<option>Engineering</option>
<option>Finance</option>
</tk-select>
</tk-field>Put the select in a aria-label. Inside a Field, set required and disabled on the Field.
Anatomy
| Part | What it is |
|---|---|
tk-select |
the element; one Tab stop |
| field | shows the chosen value, or the prompt, and a chevron; fills its container |
| chevron | turns over while the menu is open; hidden when read-only |
| menu | the option list; as wide as the field; opens below it, or above near the bottom of the screen |
| option | one row; a check marks the chosen one |
| group header | an optional heading over a run of options; it can't be chosen |
The menu shows about five and a half rows, then scrolls; the cut-off row shows there is more. It is the same menu on touch screens; there is no system picker.
Variants
<tk-select variant="filter" filter-label="Status"> <option>Open</option> <option>Closed</option> </tk-select> <tk-select variant="filter" filter-label="Owner"> <option selected>Me</option> <option>Anyone</option> </tk-select>
field(default): a form field that fills its container. Name it with a Field oraria-label.filter: a compact button for a filter bar. It reads "Status" until something is chosen, then "Status: Open", and gets a tinted fill.filter-labelis required: it is the visible text and the accessible name. Don't put a filter in a Field.
Sizes
<tk-select aria-label="Sort by"> <option selected>Newest first</option> <option>Oldest first</option> </tk-select> <tk-select size="large" aria-label="Show"> <option selected>All projects</option> <option>Starred projects</option> </tk-select>
size |
Height | Padding-inline | Value text | Option row |
|---|---|---|---|---|
medium (default) |
32px | space-8 |
Body Small (14/20) | 32px |
large |
36px | space-10 |
Body Medium (16/24) | 36px |
Option labels are Label Medium (14/20) at both sizes. A row with a description is taller. The heights match Input and Button, so a form lines up. Long values and options are cut off with an ellipsis; they never wrap.
States
<tk-select aria-label="Region" disabled> <option selected>Europe</option> </tk-select> <tk-select aria-label="Currency" readonly> <option selected>Euro</option> </tk-select>
Hover doesn't change the field. Keyboard focus colors its border and thickens it; there is
no ring. The filter variant tints on hover and shows a focus ring.
disabled: can't be focused or opened, and isn't submitted. Prefer an active field, orreadonlyand a helper that says why.readonly: shows the value and submits it. The field stays focusable, but the menu doesn't open and the chevron is hidden.- Error: set
erroron the Field. The message appears below and the border turns red, even while focused.
<tk-field label="Plan" required error="Select a plan to continue">
<tk-select placeholder="Select a plan">
<option>Free</option>
<option>Pro</option>
</tk-select>
</tk-field>Options
<tk-select aria-label="Plan" placeholder="Select a plan"> <option value="free" data-description="For trying things out">Free</option> <option value="pro" data-description="Adds version history">Pro</option> <option value="team" disabled>Team (coming soon)</option> </tk-select>
- Write options as
<option>children.valuedefaults to the option's text. selectedon an option, orvalueon the select, sets the starting value. Without either, the field shows the prompt; the first option is never picked for you.disabledkeeps an option visible but skips it. Say why in its text.data-descriptionadds a short second line. Use it to help people choose, such as a price or a limit. Don't describe every option. Screen readers read it after the option's name.
<tk-select aria-label="Office" placeholder="Select an office">
<option>Remote</option>
<optgroup label="Europe">
<option>Berlin</option>
<option>Lisbon</option>
</optgroup>
</tk-select><optgroup label="…"> groups options under a header. Keyboard navigation skips headers.
Other child elements are ignored.
When to use
- Use a select for about 4 options up to a screenful, when space matters.
- Use a
Radio group for 2 or 3 options, or when people should compare the options side by side. Try asking a question with fewer options first. - Use a
Combobox for a long list, such as countries or currencies. - Use a
Multiselect when people can pick more than one. - Use a
Menu when the items run actions instead of setting a value.
Content
- Label: name what you're asking for, in sentence case, with no colon. It goes on the
Field, or in
filter-label. - Prompt: "Select a…", in sentence case, with no end punctuation. Use one only when there is no sensible default. It disappears once a value is chosen, so don't put anything people need in it.
- Options: one short line each, in sentence case, with no commas or semicolons. Order them alphabetically or in another order people can guess.
- Don't start options with an icon or emoji. Typing "S" must land on "Sweden".
- Group headers: one or two words.
- Pre-selecting: don't pre-select an answer to a question. For a setting, pre-select the current or most likely value.
- Error: say how to fix it, using the label's words: "Select your state of residence".
More in
Do / Don't
| ✅ Do | ❌ Don't |
|---|---|
| Use a radio group for 2 or 3 options | Use a select for Yes and No |
| Use a combobox for countries or other long lists | Make people scroll a select through dozens of options |
| Label the select with a Field | Use the prompt as the label |
| Leave a question empty until people answer | Pre-select an answer to a question |
| Order options so people can guess where one is | Start options with icons or emoji |
| Use a menu for actions | Put actions in a select |
Related
Field : the label, helper, and error around a select.Radio group : 2 or 3 options, all visible.Combobox : one option from a long list, by typing to filter.Multiselect : several options; it has the samefiltervariant.Country select : a combobox with the country list built in.Menu : items that run actions.Select family : how these four compare.
API
Attributes
| Attribute | Type | Default | What it does |
|---|---|---|---|
placeholder |
string | — | the prompt shown while nothing is chosen |
value |
string | '' |
the chosen option's value; '' means nothing chosen |
name |
string | — | the form field name |
required |
boolean | false |
an empty value blocks form submission; no visible marker (the Field adds the asterisk) |
disabled |
boolean | false |
not focusable, can't open, left out of the form |
readonly |
boolean | false |
focusable and submitted, but can't open or change |
autocomplete |
string | — | the autofill hint, such as country; browser support for custom fields varies |
size |
medium | large |
medium |
height 32 / 36px |
variant |
field | filter |
field |
a form field or a filter-bar button |
filter-label |
string | — | required with variant="filter": the button's text and accessible name |
Slots
| Slot | Content |
|---|---|
| default | <option> and <optgroup> elements; changes to them are picked up |
<option> takes value, selected, disabled, and data-description. <optgroup>
takes label.
Events
change fires when someone chooses a different option. It doesn't fire while they move
through the menu, or when you set value in code. Read the new value from value.
In a form, the value submits under name, and a form reset restores the starting value.
CSS
::part(trigger): the field.::part(listbox): the menu.
Tokens
| Part | Tokens |
|---|---|
| field | surface/default + border/subtle, radius-8 |
| field, focused | border/accent/focus, thickened inward by focus/border-inset |
| field, error | border/error |
| field, read-only | surface/subtle + border/subtle |
| field, disabled | surface/disabled + border/disabled + text/disabled |
| value / prompt | text/default / text/placeholder |
| chevron | icon/secondary (disabled icon/disabled), icon-size-16 |
| menu | surface/raised + shadow-2, radius-16, space-4 padding |
| option | text/default, radius-12; highlighted state/hover |
| chosen option | the check, icon/accent |
| description / group header | text/secondary |
| disabled option | text/disabled |
| filter, chosen | state/on + border/selected (state/on-hover under the pointer) |
In high contrast, the menu also gets a 1px border/default. Values per theme:
Accessibility
Built in:
- Screen readers hear a combobox, as with a native select: its name, value, open or closed, the highlighted option, and which options are disabled.
- Focus stays on the field while the menu is open. Esc, Tab, a click outside, and choosing all close it.
- Moving through the menu never changes the value. Only choosing does.
- Typing a letter jumps to a matching option; it never types text.
- The menu stays on screen when zoomed. Every row is at least 32px tall.
- With reduced motion, the chevron turns without animation.
Your part:
- Give every select a name: a Field label,
aria-label, orfilter-label. - Set
autocompleteon personal data, such as country or birth month. - Write errors as the Content rules say.
| Key | Menu closed | Menu open |
|---|---|---|
Enter / Space |
opens the menu | chooses the highlighted option and closes |
Down / Up |
opens the menu | moves the highlight, skipping disabled options; stops at the ends |
Home / End |
— | highlights the first / last option |
| a letter | opens and jumps to a match | jumps to the next match |
Alt + Up |
— | chooses the highlighted option and closes |
Esc |
— | closes without changing the value; a surrounding dialog stays open |
Tab |
moves to the next control | chooses the highlighted option and moves on |
Contrast limits
- Field border: 1.33:1 in light and 2.00:1 in dark, under the 3:1 minimum; 4.22:1 in high contrast. The edge is faint in light, so always show a label.
- Prompt text: 3.26:1 in light, 4.09:1 in dark, and 4.22:1 in high contrast, under the 4.5:1 text minimum. Don't put anything people need in it.