Skip to content
WunderUI

Select

Free

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 select

Playground

Installation

bash
npx wunderui-cli add select

Writes the source into your project with its dependencies and, on first use, the styles - see Installation.

Then import what you need:

tsx
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

Then use it:

tsx
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

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

Sort projects by

Inline content

Sort projects by

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.

  • Optional

    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.

  • Optional

    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.

PropTypeDefaultDescription
valueSelectValueType<Value, Multiple> | null—The chosen option's value, or while nothing is chosen. It is a controlled value: update it in .
defaultValueSelectValueType<Value, Multiple> | null—The value that is chosen when you do not control the select.
onValueChange(value, eventDetails) => void—Called with the value of the option the person chose. In a controlled select the field only changes when you set .
itemsRecord<string, ReactNode> | ReadonlyArray<{ label: ReactNode; value: any; }> | ReadonlyArray<Group<any>>—The options as data, pairs or a map from value to label. Gives the field its labels when the elements are built inside another component. Read from the children when you leave it out.
itemToStringLabel(itemValue) => string—Turns an object value into the text shown in the field. Needed when the values are objects.
isItemEqualToValue(itemValue, value) => boolean—Decides whether an option is the chosen value. Needed when values are objects that are not the same references. Defaults to .
multipleMultiplefalseLets people choose several options. The value becomes an array. The field shows no chips, so show the choices yourself.
openboolean—Whether the list is open, when you control it. Pair it with .
defaultOpenbooleanfalseWhether the list starts open when you do not control it.
onOpenChange(open, eventDetails) => void—Called when the list opens or closes, with the reason, such as a click, a key press, an outside press or choosing an option.
modalbooleantrueWhen , the default, the page behind the open list is inert and does not scroll. leaves the page usable.
disabledbooleanfalseDisables the select: it dims to 40 % and ignores the pointer and the keyboard.
readOnlybooleanfalseKeeps the value but stops people from choosing another option.
requiredbooleanfalseMarks the field as required for the form it belongs to.
namestring—The name the value is submitted under in a form, through a hidden input.
actionsRefRefObject<SelectRootActions | null>—A ref to imperative actions. - : Manually unmounts the select. Call this after any externally controlled closing animation finishes.
autoCompletestring—Provides a hint to the browser for autofill.
childrenReactNode—
formstring—Identifies the form that owns the hidden input. Useful when the select is rendered outside the form.
highlightItemOnHoverbooleantrueWhether moving the pointer over items should highlight them. Disabling this prop allows CSS to be differentiated from the () state.
idstring—The id of the Select.
inputRefRef<HTMLInputElement>—A ref to access the hidden input element.
itemToStringValue(itemValue) => string—When the item values are objects (), this function converts the object value to a string representation for form submission. If the shape of the object is , the value will be used automatical…
onOpenChangeComplete(open) => void—Event handler called after any animations complete when the select popup is opened or closed.

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.

PropTypeDefaultDescription
variant"field" | "inline""field" (default) is the full-width form field with a border. is a compact trigger with no border that is only as wide as its label and sits inside a sentence, a toolbar, a title or a row; it replaces InlineSelect. Give an inline trigger an , because it has no visible label.
classNamestring | (state) => string | undefined—Extra classes for the field, for example for the large height.
disabledboolean—Disables this trigger only. Prefer on the root.
childrenReactNode—
nativeButtonbooleantrueWhether the component renders a native element when replacing it via the prop. Set to if the rendered element is not a button (for example, ).

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.

PropTypeDefaultDescription
placeholderReactNode—The text shown in the field while nothing is chosen, in the quieter tertiary colour.
childrenReactNode | (value) => ReactNode—Replaces the text in the field with your own content, or a function of the chosen value.
classNamestring | (state) => string | undefined—Extra classes for the text in the field. It is a flex row, so an icon and a label sit side by side.

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.

PropTypeDefaultDescription
classNamestring | (state) => string | undefined—Extra classes for the list panel, for example to make it wider than the field.
finalFocusboolean | RefObject<HTMLElement | null> | (closeType) => unknown—Where focus goes when the list closes. It returns to the field by default.
childrenReactNode—

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.

PropTypeDefaultDescription
valueanynullThe value the select holds when this option is chosen.
labelstring—The text used for type-ahead and for the field when the children are not plain text.
disabledbooleanfalseStops the option from being highlighted or chosen.
classNamestring | (state) => string | undefined—Extra classes for the row.
childrenReactNode—
nativeButtonbooleanfalseWhether the component renders a native element when replacing it via the prop. Set to if the rendered element is a native button.

PropTypeDefaultDescription
actionsRefRefObject<SelectRootActions | null>—A ref to imperative actions. - : Manually unmounts the select. Call this after any externally controlled closing animation finishes.
autoCompletestring—Provides a hint to the browser for autofill.
childrenReactNode—
defaultOpenbooleanfalseWhether the select popup is initially open. To render a controlled select popup, use the prop instead.
defaultValueSelectValueType<Value, Multiple> | null—The uncontrolled value of the select when it's initially rendered. To render a controlled select, use the prop instead.
disabledbooleanfalseWhether the component should ignore user interaction.
formstring—Identifies the form that owns the hidden input. Useful when the select is rendered outside the form.
highlightItemOnHoverbooleantrueWhether moving the pointer over items should highlight them. Disabling this prop allows CSS to be differentiated from the () state.
idstring—The id of the Select.
inputRefRef<HTMLInputElement>—A ref to access the hidden input element.
isItemEqualToValue(itemValue, value) => boolean—Custom comparison logic used to determine if a select item value matches the current selected value. Useful when item values are objects without matching referentially. Defaults to comparison.
itemsRecord<string, ReactNode> | ReadonlyArray<{ label: ReactNode; value: any; }> | ReadonlyArray<Group<any>>—Data structure of the items rendered in the select popup. When specified, renders the label of the selected item instead of the raw value.
itemToStringLabel(itemValue) => string—When the item values are objects (), this function converts the object value to a string representation for display in the trigger. If the shape of the object is , the label will be used auto…
itemToStringValue(itemValue) => string—When the item values are objects (), this function converts the object value to a string representation for form submission. If the shape of the object is , the value will be used automatical…
modalbooleantrueDetermines if the select enters a modal state when open. - : user interaction is limited to the select: document page scroll is locked and pointer interactions on outside elements are disabled. - : user interaction with the re…
multipleMultiplefalseWhether multiple items can be selected.
namestring—Identifies the field when a form is submitted.
onOpenChange(open, eventDetails) => void—Event handler called when the select popup is opened or closed.
onOpenChangeComplete(open) => void—Event handler called after any animations complete when the select popup is opened or closed.
onValueChange(value, eventDetails) => void—Event handler called when the value of the select changes.
openboolean—Whether the select popup is currently open.
readOnlybooleanfalseWhether the user should be unable to choose a different option from the select popup.
requiredbooleanfalseWhether the user must choose a value before submitting a form.
valueSelectValueType<Value, Multiple> | null—The value of the select. Use when controlled.

PropTypeDefaultDescription
childrenReactNode—
finalFocusboolean | RefObject<HTMLElement | null> | (closeType) => unknown—Determines the element to focus when the select popup is closed. - : Do not move focus. - : Move focus based on the default behavior (trigger or previously focused element). - : Move focus to the ref element. - `fun…

PropTypeDefaultDescription
childrenReactNode—
disabledbooleanfalseWhether the component should ignore user interaction.
labelstring—Specifies the text label to use when the item is matched during keyboard text navigation. Defaults to the item text content if not provided.
nativeButtonbooleanfalseWhether the component renders a native element when replacing it via the prop. Set to if the rendered element is a native button.
valueanynullA unique value that identifies this select item.

PropTypeDefaultDescription
childrenReactNode—
disabledboolean—Whether the component should ignore user interaction.
nativeButtonbooleantrueWhether the component renders a native element when replacing it via the prop. Set to if the rendered element is not a button (for example, ).

PropTypeDefaultDescription
childrenReactNode | (value) => ReactNode—Accepts a function that returns a to format the selected value. Treat the value as read-only: in mode it may be a shared frozen array when nothing is selected.
placeholderReactNode—The placeholder value to display when no value is selected. This is overridden by if specified, or by a null item's label in .

PropTypeDefaultDescription
actionsRefRefObject<SelectRootActions | null>—A ref to imperative actions. - : Manually unmounts the select. Call this after any externally controlled closing animation finishes.
autoCompletestring—Provides a hint to the browser for autofill.
childrenReactNode—
defaultOpenbooleanfalseWhether the select popup is initially open. To render a controlled select popup, use the prop instead.
defaultValueSelectValueType<Value, Multiple> | null—The uncontrolled value of the select when it's initially rendered. To render a controlled select, use the prop instead.
disabledbooleanfalseWhether the component should ignore user interaction.
formstring—Identifies the form that owns the hidden input. Useful when the select is rendered outside the form.
highlightItemOnHoverbooleantrueWhether moving the pointer over items should highlight them. Disabling this prop allows CSS to be differentiated from the () state.
idstring—The id of the Select.
inputRefRef<HTMLInputElement>—A ref to access the hidden input element.
isItemEqualToValue(itemValue, value) => boolean—Custom comparison logic used to determine if a select item value matches the current selected value. Useful when item values are objects without matching referentially. Defaults to comparison.
itemsRecord<string, ReactNode> | ReadonlyArray<{ label: ReactNode; value: any; }> | ReadonlyArray<Group<any>>—Data structure of the items rendered in the select popup. When specified, renders the label of the selected item instead of the raw value.
itemToStringLabel(itemValue) => string—When the item values are objects (), this function converts the object value to a string representation for display in the trigger. If the shape of the object is , the label will be used auto…
itemToStringValue(itemValue) => string—When the item values are objects (), this function converts the object value to a string representation for form submission. If the shape of the object is , the value will be used automatical…
modalbooleantrueDetermines if the select enters a modal state when open. - : user interaction is limited to the select: document page scroll is locked and pointer interactions on outside elements are disabled. - : user interaction with the re…
multipleMultiplefalseWhether multiple items can be selected.
namestring—Identifies the field when a form is submitted.
onOpenChange(open, eventDetails) => void—Event handler called when the select popup is opened or closed.
onOpenChangeComplete(open) => void—Event handler called after any animations complete when the select popup is opened or closed.
onValueChange(value, eventDetails) => void—Event handler called when the value of the select changes.
openboolean—Whether the select popup is currently open.
readOnlybooleanfalseWhether the user should be unable to choose a different option from the select popup.
requiredbooleanfalseWhether the user must choose a value before submitting a form.
valueSelectValueType<Value, Multiple> | null—The value of the select. Use when controlled.

PropTypeDefaultDescription
childrenReactNode—
finalFocusboolean | RefObject<HTMLElement | null> | (closeType) => unknown—Determines the element to focus when the select popup is closed. - : Do not move focus. - : Move focus based on the default behavior (trigger or previously focused element). - : Move focus to the ref element. - `fun…

PropTypeDefaultDescription
childrenReactNode—
disabledbooleanfalseWhether the component should ignore user interaction.
labelstring—Specifies the text label to use when the item is matched during keyboard text navigation. Defaults to the item text content if not provided.
nativeButtonbooleanfalseWhether the component renders a native element when replacing it via the prop. Set to if the rendered element is a native button.
valueanynullA unique value that identifies this select item.

PropTypeDefaultDescription
childrenReactNode—
disabledboolean—Whether the component should ignore user interaction.
nativeButtonbooleantrueWhether the component renders a native element when replacing it via the prop. Set to if the rendered element is not a button (for example, ).
variant"field" | "inline"—

PropTypeDefaultDescription
childrenReactNode | (value) => ReactNode—Accepts a function that returns a to format the selected value. Treat the value as read-only: in mode it may be a shared frozen array when nothing is selected.
placeholderReactNode—The placeholder value to display when no value is selected. This is overridden by if specified, or by a null item's label in .

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

MCPget_component("select")
Installnpx wunderui-cli add select

The full library index for assistants is at /llms.txt.