Noctis

Select

A select: a field-shaped trigger opens a floating listbox of options — with grouped sections, optional leading icons, a placeholder, and a trailing check marking the current value. The trigger wears the field look (a calm, ring-less accent border on focus); the popup is an elevated surface.

Basic

A Select.Trigger (holding a Select.Value and a Select.Icon) opens the Select.Popup. Pass items on Select.Root so Select.Value renders the chosen item's label rather than its raw value, and defaultValue for an initial selection.

Sizes

Select.Root takes a size (md | lg, default md) shared with the trigger — it sets the trigger's control height, inline padding, and value type size off the shared, density-aware control scale, matching the rest of the field family. The popup rows keep one size, so the list reads the same comfortable density whichever trigger opens it.

Leading content

Give a Select.Item an icon — a glyph, a colour dot, or an avatar — and it sits in a reserved leading column so labels stay aligned whether or not a row has one. The selected row's check trails the row, so unselected labels stay flush to the leading edge. This is the status/priority/assignee-picker shape.

Multiple

Pass multiple on Select.Root for a multi-select: picking a row toggles it without closing the popup, every selected row keeps its check, and Select.Value summarises the selection as a localized "N selected" in the trigger (override it with a child function for a custom display). Seed it with an array defaultValue.

Placement

The popup opens in one of two placements. By default it overlays the trigger (item-aligned, so the selected row lines up over the trigger's value — the macOS-native feel; mouse input only, and auto-disabled when there isn't room). Pass alignItemWithTrigger={false} on Select.Popup to anchor it below the trigger instead, like a standard dropdown.

Long lists

A long list caps at the available viewport height and scrolls. The list's scrollbar is hidden — sticky scroll arrows appear at the popup's top and bottom edges and scroll on hover (the Radix/macOS pattern), while the wheel, trackpad, and arrow keys scroll as usual. The keyboard highlight always stays in view.

Groups

Wrap related options in a Select.Group with a Select.GroupLabel — the label is muted, non-interactive, and announced as the group's name — and divide groups with a Select.Separator.

In a field

Drop a Select straight into a Field.Root — its trigger auto-wires to the field, so the Field.Label, Field.Description, and Field.Error are associated for assistive tech and the trigger turns invalid (a danger border) when the field does. Use nativeLabel={false} on the label since it labels a button, mark the field required, and write the placeholder as a prompt to act ("Select a framework"), not a fake value.

Used to scaffold the starter project.

Controlled

Drive the selection yourself with value + onValueChange on Select.Root (set value to null to clear it).

Selected: apple

Select or Combobox?

Reach for Select when the options are a short, fixed list (roughly ten or fewer) the user picks from — it shows only the chosen value and never accepts typed text. When the list is long or the user needs to filter by typing, reach for Combobox instead.

Keyboard

KeyAction
Enter / Space / / On the trigger: open the popup, highlighting the selected option (or the first / last).
Alt + Open the popup without moving the highlight.
/ Move the highlight to the next / previous option.
PageDown / PageUpJump the highlight by a page (about ten options).
Home / EndFirst / last option.
Enter / SpaceSelect the highlighted option and close the popup, returning focus to the trigger.
Alt + Select the highlighted option and close the popup.
EscClose the popup without changing the value; return focus to the trigger.
TabClose the popup and move focus on.
CharactersTypeahead — jump to the option whose label matches what you type; a non-matching key never moves the highlight, and repeats of one character cycle the matches.

Accessibility

  • The trigger follows the APG select-only combobox pattern: it is a role="combobox" button with aria-expanded, focus stays on it (the popup is a role="listbox" of role="option"s driven by aria-activedescendant), and the chosen option carries aria-selected.
  • Disabled options stay reachable by the keyboard and are announced as disabled — they just can't be selected. Removing them from navigation would hide why a choice is unavailable from screen-reader users.
  • The scroll arrows are a pointer affordance (they scroll on hover and never render for touch), so they are aria-hidden — keyboard users scroll with the arrow keys.
  • In a Field, the trigger wires aria-describedby to the description and error, takes aria-invalid while invalid, and aria-required when the field is required; the visual required marker is decorative (aria-hidden).
  • Multiple selects convey their count through the trigger's "N selected" summary, and every selected option keeps aria-selected.
  • The chevron, the leading column, and the popup alignment are all direction-aware, so the select mirrors under RTL by construction.

Anatomy

Compose a select from its parts. Select.Root owns the value and open state (it accepts every Base UI Select.Root prop — value, defaultValue, onValueChange, items, multiple, readOnly, required, modal, plus the name/form props — and adds size and an additive invalid override).

  • Select.Root — owns the value and open state and shares the control size, multiple, and invalid; renders no element of its own.
  • Select.Trigger — the field-shaped control that opens the popup; reads the root's size and paints the danger border while invalid, the quiet chrome while readOnly.
  • Select.Value — renders the selected label inside the trigger (or a localized "N selected" under multiple), falling back to placeholder.
  • Select.Icon — the trailing chevron marking the control as a select.
  • Select.Backdrop — the modal scrim; rendered for you by Select.Popup only when the select is modal (off by default, so the page keeps scrolling while the popup is open), transparent.
  • Select.Popup — the floating, elevated, animated listbox. Props: side (default bottom), align (default start), sideOffset, alignItemWithTrigger (default true), collisionPadding.
  • Select.ScrollUpArrow + Select.ScrollDownArrow — the sticky scroll affordances; rendered for you by Select.Popup, appearing only while the list can scroll.
  • Select.List — the scrollable list inside the popup; rendered for you.
  • Select.Item — a selectable option carrying its value, plus an optional leading icon and description, with a trailing check on the selected row.
  • Select.ItemIcon / Select.ItemText / Select.ItemDescription / Select.ItemIndicator — the row's leading column, label, muted second line, and selected-check; rendered for you by Select.Item (via its icon/description props), composed by hand only for custom inner structure.
  • Select.Group + Select.GroupLabel — a labelled section of related options.
  • Select.Separator — a presentational hairline between groups.

Every rendered part carries a data-slot (select-trigger, select-value, select-icon, select-backdrop, select-popup, select-scroll-up-arrow, select-scroll-down-arrow, select-list, select-item, select-item-icon, select-item-text, select-item-description, select-item-indicator, select-group, select-group-label, select-separator) for host-side styling — pair it with the Base UI state attributes (data-open, data-placeholder, data-invalid, data-readonly, data-highlighted, data-selected, data-disabled).

On surfaces

The same control re-tuned across the elevation scopes — the root canvas, an elevated panel, a menu, and a sunken well. It stays legible on every layer.

root
elevated
menu
sunken

Design tokens

Generated from the component's declaration — the same graph that mints the CSS, so a variable name or its resolution default can't drift. The minted tokens are the public override seam: set one on any ancestor and every select in that region retunes — e.g. .dense { --noctis-select-item-height: 1.75rem; } tightens every popup opened beneath it. Knobs that aren't minted are reached through the part's data-slot. See Customization for the full override ladder and Tokens for the whole graph.

Token

API reference

Generated from the component's types — every prop, type, default, and description comes straight from the source. Each part gets its own table; parts that only forward to Base UI list just the props they pass through.

Select.Root

Prop

Select.Trigger

Prop

Select.Value

Prop

Select.Icon

Prop

Select.Backdrop

Prop

Select.Popup

Prop

Select.ScrollUpArrow

Prop

Select.ScrollDownArrow

Prop

Select.List

Prop

Select.Item

Prop

Select.ItemIcon

Prop

Select.ItemText

Prop

Select.ItemDescription

Prop

Select.ItemIndicator

Prop

Select.Group

Prop

Select.GroupLabel

Prop

Select.Separator

Prop

AttributeDescription
data-slotThe slot marker on every rendered part.
data-sizeThe trigger size, mirroring the `size` prop (`md`/`lg`); the component layer keys the trigger's metrics off it.
data-openPresent on the trigger and popup while the select is open.
data-closedPresent on the trigger and popup while the select is closed.
data-placeholderPresent on the trigger when no value is selected (the placeholder is showing).
data-invalidPresent on the trigger while the field is invalid — paints the danger border/ring.
data-readonlyPresent on the trigger while the select is read-only — quiet, non-interactive chrome.
data-highlightedPresent on the item the pointer or keyboard is currently over.
data-selectedPresent on the currently selected item (and its trailing check).
data-disabledPresent on a disabled trigger or item.
data-visiblePresent on a scroll arrow while the list can scroll in its direction (drives its fade).
data-sideThe side of the anchor the popup actually rendered on (`bottom`, `top`, …).
data-starting-stylePresent on the popup/backdrop/scroll-arrows for the first frame after mount — the transition's start state.
data-ending-stylePresent on the popup/backdrop/scroll-arrows while they transition out before unmounting.