Select
A styled select: a field that shows the chosen option and opens a list below it. Pick one and the list closes.
npx wunderui-cli add selectPlayground
Installation
npx wunderui-cli add selectWrites the source into your project with its dependencies and, on first use, the styles - see Installation.
Then import what you need:
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"Then use it:
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
export function StatusField() {
return (
<Select>
<SelectTrigger aria-label="Status">
<SelectValue placeholder="Select a status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="backlog">Backlog</SelectItem>
<SelectItem value="in-progress">In progress</SelectItem>
<SelectItem value="done">Done</SelectItem>
</SelectContent>
</Select>
)
}Expected result: You see a field with the placeholder Select a status and a chevron. A click opens three options below it; choosing In progress closes the list and puts the label in the field.
When to use
Use Select for
- Choosing one option from a short, fixed list, such as a status, a role or a sort order
- A form field that shows the chosen option in the field itself
- Lists of about 3 to 12 options that people know by sight
- Inline (): a value inside a sentence, such as Show comments from the last 30 days
- Inline: a compact choice in a toolbar, a card title or a table row, such as a sort order, a period or a status
Use something else for
- Combobox for a long list people need to search
- Radio Group for two to four options that should all be visible
- Dropdown Menu for actions, not a value for a field
Content
An option is a label, with an icon in front when it helps tell options apart, and the field shows the icon too. Keep labels short: the list is as wide as the field.
Sizes
The field is 36 px high, or 40 px in the Large size, and comfortable density adds 8 px to each. The label sits 8 px above the field.
States
Hover, focus and open are real states, and the border stays indigo while open. An invalid select turns red with a reason below, and a disabled one dims and ignores input.
Inline
SelectTrigger variant="inline" has no border and sits inside a sentence, a toolbar or a title; the text around it says what is being chosen. It takes the type of that text, 14 px in a sentence or toolbar and larger in a heading when you set the size on the trigger. Hover fills it with the page colour, focus draws the indigo border and ring, a placeholder is quieter than a value and a disabled select dims to 40 %. The list opens below it and is the same as in the field variant. It replaces InlineSelect.
Inline
Inline content
Inline states
Keyboard interactions
The field is a native button, so the browser handles focus. Once the list is open, focus is inside it and the arrow keys move between options.
- EnterorSpaceorArrowDown
- On the field: opens the list and highlights the chosen option, or the first one.
- ArrowDownorArrowUp
- Moves the highlight to the next or previous option. It stops at the ends.
- HomeorEnd
- Moves the highlight to the first or the last option.
- A–Z
- Type-ahead: typing the start of a label moves the highlight to the next option that begins with it.
- EnterorSpace
- Chooses the highlighted option, closes the list and returns focus to the field.
- Escape
- Closes the list without a change and returns focus to the field.
- Tab
- Closes the list when focus leaves it.
Behavior and motion
What the select does with its value, focus, the list and motion.
- Options
- The field is a combobox, the list a listbox and each option an option. The chosen option is marked selected and shows a check.
- The label in the field
- The field shows the chosen option's label, not its value. Labels come from the item children, or from an items prop when options are built inside another component, and an icon shows in the field too.
- Placeholder
- The placeholder shows in the tertiary text colour until an option is chosen.
- Modal
- A select is modal by default: the page behind it is inert and does not scroll while the list is open. Turn modal off to keep the page usable, as the previews here do.
- Position and size
- The list opens 4 px below the field, aligned to its start and as wide as it (8 rem at least). It flips above when there is no room and scrolls when it is taller than the space left.
- Open field
- While the list is open the border of the field stays indigo, so the field and its list read as one.
- Invalid and disabled
- An invalid trigger turns the border and focus ring red, so say why in a line under the field, linked with aria-describedby. A disabled select dims to 40% and ignores pointer and keyboard.
- Previews in these docs
- The real list floats over the page. The previews here draw it inside the card, only while the whole card is on screen, and behave like the real one when you point at a card or focus it.
- Opening and closing
- The list fades in and scales up from the field while sliding 4 px in from below. Closing is one step faster.
- Reduced motion
- With reduced motion the transition is off and the list appears and disappears at once.
- Inline width
- The inline trigger is only as wide as the current label, so text after it moves when a longer or shorter option is chosen. Keep labels of similar length.
Anatomy
The parts a select is made of, numbered in the figure. The field shows the chosen option, the list holds the options, and the check marks the chosen one.
- data-slot="select"
The root. It holds the chosen value and open state and renders nothing itself, and it reads option labels from its items, so the field shows Germany for the value de before the list ever opens.
- data-slot="select-trigger"
The field: a button that shows the chosen option and opens the list, 36 px high with an indigo border while open. Give it a visible label or an aria-label.
- data-slot="select-value"
The text in the field: the chosen option's label, or the placeholder in a quieter colour while nothing is chosen. An option with an icon shows the icon here too.
The 16 px chevron at the right of the field. It is part of the trigger and hidden from assistive technology.
- data-slot="select-content"
The list panel. It opens below the field at the same width and scrolls when it runs out of room, and its 12 px corner is concentric with the 8 px rows inside its 4 px padding.
- data-slot="select-item"
One option, 32 px high. Its value is what the select holds and its children are the label, with an optional icon in front; the highlighted option gets the page fill.
A 16 px check at the right of the chosen option.
Best practices
- Do
Give the select a visible label, joined to the trigger with an id. The placeholder is a hint such as Select a status, not the name of the field.
- Do
Set a placeholder for a field that starts empty and say what is wanted: Select a status, not just Select.
- Do
Keep the labels short and parallel. The list is as wide as the field, and a long label is cut off.
- Do
Mark an invalid select as invalid on the trigger, and say why in a line under it, linked with aria-describedby.
- Don’t
Don't use it for a long list. Past a dozen options people scroll instead of reading; a Combobox lets them type.
- Don’t
Don't use it for two or three options that fit on the screen. Radio buttons show every option at once and need one click less.
- Don’t
Don't use it for actions. Choosing an option sets a value; a menu of actions belongs in a Dropdown Menu.
- Do
Inline: Give every trigger an aria-label that names what is chosen, such as Sort order or Status of WUI-482. There is no visible label, and the announced value alone doesn't say what the choice is for.
- Don’t
Inline: Don't use it as a form field. Without a border it doesn't look like an input; use Select with a label inside a Field.
- Don’t
Inline: Don't let long labels push the sentence around. The trigger is as wide as the current label, so text after it moves when the value changes; keep labels about the same length.
API reference
Select wraps the Base UI select and passes its props on. The root takes the value and open state; every part also takes className, style and render. SelectTrigger takes `variant`: `field` (default) is the full form field, `inline` the compact borderless trigger for a sentence, a toolbar or a row. The list and its options are the same for both.
The root. It holds the chosen value and open state and renders nothing itself, and it reads option labels from its items, so the field shows Germany for the value de before the list ever opens.
The field: a button that shows the chosen option and opens the list, 36 px high with an indigo border while open. Give it a visible label or an aria-label.
The text in the field: the chosen option's label, or the placeholder in a quieter colour while nothing is chosen. An option with an icon shows the icon here too.
The list panel. It opens below the field at the same width and scrolls when it runs out of room, and its 12 px corner is concentric with the 8 px rows inside its 4 px padding.
One option, 32 px high. Its value is what the select holds and its children are the label, with an optional icon in front; the highlighted option gets the page fill.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
get_component("select")npx wunderui-cli add selectThe full library index for assistants is at /llms.txt.