Select

A dropdown for choosing from a list of options.

Import

import { Select } from '@oztix/roadie-components/select'

Examples

Default

Pass string children to Select.Item for automatic ItemText + ItemIndicator wrapping. Use Select.Content as a convenience wrapper for Portal + Positioner + Popup.

<Select defaultValue='powderfinger'>
  <Select.Trigger>
    <Select.Value placeholder='Pick a band' />
    <Select.Icon />
  </Select.Trigger>
  <Select.Content>
    <Select.Item value='powderfinger'>Powderfinger</Select.Item>
    <Select.Item value='custard'>Custard</Select.Item>
    <Select.Item value='regurgitator'>Regurgitator</Select.Item>
    <Select.Item value='violent-soho'>Violent Soho</Select.Item>
    <Select.Item value='dz-deathrays'>DZ Deathrays</Select.Item>
  </Select.Content>
</Select>

Emphasis

<div className='grid gap-2'>
  <Select defaultValue='go-betweens'>
    <Select.Trigger emphasis='normal'>
      <Select.Value />
      <Select.Icon />
    </Select.Trigger>
    <Select.Content>
      <Select.Item value='go-betweens'>The Go-Betweens</Select.Item>
      <Select.Item value='saints'>The Saints</Select.Item>
    </Select.Content>
  </Select>
  <Select defaultValue='bee-gees'>
    <Select.Trigger emphasis='subtle'>
      <Select.Value />
      <Select.Icon />
    </Select.Trigger>
    <Select.Content>
      <Select.Item value='bee-gees'>Bee Gees</Select.Item>
      <Select.Item value='savage-garden'>Savage Garden</Select.Item>
    </Select.Content>
  </Select>
</div>

Sizes

<div className='grid gap-2'>
  <Select defaultValue='ball-park-music'>
    <Select.Trigger size='sm'>
      <Select.Value />
      <Select.Icon />
    </Select.Trigger>
    <Select.Content>
      <Select.Item value='ball-park-music'>Ball Park Music</Select.Item>
    </Select.Content>
  </Select>
  <Select defaultValue='cub-sport'>
    <Select.Trigger size='md'>
      <Select.Value />
      <Select.Icon />
    </Select.Trigger>
    <Select.Content>
      <Select.Item value='cub-sport'>Cub Sport</Select.Item>
    </Select.Content>
  </Select>
  <Select defaultValue='jungle-giants'>
    <Select.Trigger size='lg'>
      <Select.Value />
      <Select.Icon />
    </Select.Trigger>
    <Select.Content>
      <Select.Item value='jungle-giants'>The Jungle Giants</Select.Item>
    </Select.Content>
  </Select>
</div>

States

The trigger uses is-interactive-field for state-based colour transitions: neutral at rest, accent on focus/open, danger when invalid.

<div className='grid gap-4'>
  <div className='grid gap-1'>
    <p className='text-sm text-subtle'>Default</p>
    <Select defaultValue='goon-sax'>
      <Select.Trigger>
        <Select.Value />
        <Select.Icon />
      </Select.Trigger>
      <Select.Content>
        <Select.Item value='goon-sax'>The Goon Sax</Select.Item>
      </Select.Content>
    </Select>
  </div>
  <div className='grid gap-1'>
    <p className='text-sm text-subtle'>Disabled</p>
    <Select defaultValue='powderfinger' disabled>
      <Select.Trigger>
        <Select.Value />
        <Select.Icon />
      </Select.Trigger>
      <Select.Content>
        <Select.Item value='powderfinger'>Powderfinger</Select.Item>
      </Select.Content>
    </Select>
  </div>
</div>

Composition

With Field

Wrap Select in Field for consistent layout, labels, and error handling. Field provides spacing, label wiring, and context flow automatically.

<Field required>
  <Field.Label showIndicator>Favourite band</Field.Label>
  <Select defaultValue='powderfinger'>
    <Select.Trigger>
      <Select.Value placeholder='Pick a band' />
      <Select.Icon />
    </Select.Trigger>
    <Select.Content>
      <Select.Item value='powderfinger'>Powderfinger</Select.Item>
      <Select.Item value='custard'>Custard</Select.Item>
      <Select.Item value='regurgitator'>Regurgitator</Select.Item>
    </Select.Content>
  </Select>
</Field>

With Field and error

<Field invalid required>
  <Field.Label showIndicator>Favourite band</Field.Label>
  <Select>
    <Select.Trigger>
      <Select.Value placeholder='Pick a band' />
      <Select.Icon />
    </Select.Trigger>
    <Select.Content>
      <Select.Item value='powderfinger'>Powderfinger</Select.Item>
      <Select.Item value='custard'>Custard</Select.Item>
      <Select.Item value='regurgitator'>Regurgitator</Select.Item>
    </Select.Content>
  </Select>
  <Field.ErrorText>Please select a band.</Field.ErrorText>
</Field>

With standalone label

Select also has its own Select.Label for standalone usage without a Field wrapper. Use showIndicator to show a required or optional indicator.

<Select defaultValue='powderfinger' required>
  <div className='grid gap-1.5'>
    <Select.Label showIndicator>Favourite band</Select.Label>
    <Select.Trigger>
      <Select.Value placeholder='Pick a band' />
      <Select.Icon />
    </Select.Trigger>
  </div>
  <Select.Content>
    <Select.Item value='powderfinger'>Powderfinger</Select.Item>
    <Select.Item value='custard'>Custard</Select.Item>
    <Select.Item value='regurgitator'>Regurgitator</Select.Item>
  </Select.Content>
</Select>

With groups

<Select defaultValue='powderfinger'>
  <Select.Trigger>
    <Select.Value placeholder='Pick a band' />
    <Select.Icon />
  </Select.Trigger>
  <Select.Content>
    <Select.Group>
      <Select.GroupLabel>Brisbane</Select.GroupLabel>
      <Select.Item value='powderfinger'>Powderfinger</Select.Item>
      <Select.Item value='custard'>Custard</Select.Item>
      <Select.Item value='go-betweens'>The Go-Betweens</Select.Item>
      <Select.Item value='saints'>The Saints</Select.Item>
    </Select.Group>
    <Select.Group>
      <Select.GroupLabel>Gold Coast</Select.GroupLabel>
      <Select.Item value='violent-soho'>Violent Soho</Select.Item>
      <Select.Item value='dz-deathrays'>DZ Deathrays</Select.Item>
      <Select.Item value='goon-sax'>The Goon Sax</Select.Item>
    </Select.Group>
  </Select.Content>
</Select>

Guidelines

When to use Select

Use Select when the list of options is known, short, and fixed. The user picks from a closed set — they don't need to search or type a custom value.

  • Country/state dropdowns (small set)
  • Status or category pickers
  • Sort-by controls
  • Any list under ~15 items where scanning is faster than typing

When to use Combobox instead

If users are likely to search the list, the list is long (15+ items), or options load asynchronously, use Combobox.

QuestionSelectCombobox
Can the user type to filter?No (typeahead only)Yes
Is the list longer than ~15 items?AvoidPreferred
Are options loaded from an API?NoYes
Can the user enter a custom value?NoPossible
Does the user know roughly what they want?Maybe not — they browseYes — they search

Keyboard behaviour

Select delegates all keyboard handling to Base UI:

  • Arrow keys navigate the list
  • Typeahead jumps to items by first letter(s)
  • Enter / Space confirms the highlighted item
  • Escape closes the popup

Accessibility

  • Always pair with Select.Label or an aria-label on the trigger.
  • Select.ItemIndicator provides a visible check for the selected item — include it for clarity.
  • The popup locks scroll by default (modal={true}). Set modal={false} if the select appears in a non-blocking context like a toolbar.

API reference

Select

Base UI
inputRef?Ref<HTMLInputElement>

A ref to access the hidden input element.

name?string

Identifies the field when a form is submitted.

autoComplete?string

Provides a hint to the browser for autofill. @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/autocomplete

id?string

The id of the Select.

required?boolean

Whether the user must choose a value before submitting a form.

Defaults to false.

readOnly?boolean

Whether the user should be unable to choose a different option from the select popup.

Defaults to false.

disabled?boolean

Whether the component should ignore user interaction.

Defaults to false.

multiple?false

Whether multiple items can be selected.

Defaults to false.

highlightItemOnHover?boolean

Whether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.

Defaults to true.

defaultOpen?boolean

Whether the select popup is initially open. To render a controlled select popup, use the `open` prop instead.

Defaults to false.

onOpenChange?((open: boolean, eventDetails: SelectRootChangeEventDetails) => void)

Event handler called when the select popup is opened or closed.

onOpenChangeComplete?((open: boolean) => void)

Event handler called after any animations complete when the select popup is opened or closed.

open?boolean

Whether the select popup is currently open.

modal?boolean

Determines if the select enters a modal state when open. - `true`: user interaction is limited to the select: document page scroll is locked and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed.

Defaults to true.

actionsRef?RefObject<SelectRootActions | null>

A ref to imperative actions. - `unmount`: When specified, the select will not be unmounted when closed. Instead, the `unmount` function must be called to unmount the select manually. Useful when the select's animation is controlled by an external library.

items?Record<string, ReactNode> | readonly { label: ReactNode; value: any; }[] | readonly Group<any>[]

Data structure of the items rendered in the select popup. When specified, `<Select.Value>` renders the label of the selected item instead of the raw value. @example ```tsx const items = { sans: 'Sans-serif', serif: 'Serif', mono: 'Monospace', cursive: 'Cursive', }; <Select.Root items={items} /> ```

itemToStringLabel?((itemValue: unknown) => string)

When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for display in the trigger. If the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop.

itemToStringValue?((itemValue: unknown) => string)

When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for form submission. If the shape of the object is `{ value, label }`, the value will be used automatically without needing to specify this prop.

isItemEqualToValue?((itemValue: unknown, value: unknown) => 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 `Object.is` comparison.

defaultValue?unknown

The uncontrolled value of the select when it’s initially rendered. To render a controlled select, use the `value` prop instead.

value?unknown

The value of the select. Use when controlled.

onValueChange?((value: unknown, eventDetails: SelectRootChangeEventDetails) => void)

Event handler called when the value of the select changes.

invalid?boolean

Select.Content

Inherited from SelectPopupProps

finalFocus?boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)

Determines the element to focus when the select popup is closed. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.

Select.ErrorText

No additional props — forwards all standard HTML attributes to the underlying element.

Select.Group

Base UI

No additional props — forwards all standard HTML attributes to the underlying element.

Select.GroupLabel

Base UI

No additional props — forwards all standard HTML attributes to the underlying element.

Select.HelperText

No additional props — forwards all standard HTML attributes to the underlying element.

Select.Icon

Base UI

No additional props — forwards all standard HTML attributes to the underlying element.

Select.Item

Base UI

Inherited from SelectItemProps

value?any

A unique value that identifies this select item.

Defaults to null.

disabled?boolean

Whether the component should ignore user interaction.

Defaults to false.

label?string

Specifies the text label to use when the item is matched during keyboard text navigation. Defaults to the item text content if not provided.

Inherited from NonNativeButtonProps

nativeButton?boolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button.

Defaults to false.

Select.ItemIndicator

Base UI

Inherited from SelectItemIndicatorProps

keepMounted?boolean

Whether to keep the HTML element in the DOM when the item is not selected.

Select.ItemText

Base UI

No additional props — forwards all standard HTML attributes to the underlying element.

Select.Label

Base UI
showIndicator?boolean

Select.Popup

Base UI

Inherited from SelectPopupProps

finalFocus?boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)

Determines the element to focus when the select popup is closed. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.

Select.Portal

Base UI

Inherited from Props

container?HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null

A parent element to render the portal element into.

Select.Positioner

Base UI

Inherited from SelectPositionerProps

alignItemWithTrigger?boolean

Whether the positioner overlaps the trigger so the selected item's text is aligned with the trigger's value text. This only applies to mouse input and is automatically disabled if there is not enough space.

Defaults to true.

Inherited from UseAnchorPositioningSharedParameters

anchor?any

An element to position the popup against. By default, the popup will be positioned against the trigger.

positionMethod?"absolute" | "fixed"

Determines which CSS `position` property to use.

Defaults to 'absolute'.

side?"top" | "bottom" | "left" | "right" | "inline-end" | "inline-start"

Which side of the anchor element to align the popup against. May automatically change to avoid collisions.

Defaults to 'bottom'.

sideOffset?number | OffsetFunction

Distance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. - `data.side`: which side of the anchor element the positioner is aligned against. - `data.align`: how the positioner is aligned relative to the specified side. @example ```jsx <Positioner sideOffset={({ side, align, anchor, positioner }) => { return side === 'top' || side === 'bottom' ? anchor.height : anchor.width; }} /> ```

Defaults to 0.

align?"center" | "start" | "end"

How to align the popup relative to the specified side.

Defaults to 'center'.

alignOffset?number | OffsetFunction

Additional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. - `data.side`: which side of the anchor element the positioner is aligned against. - `data.align`: how the positioner is aligned relative to the specified side. @example ```jsx <Positioner alignOffset={({ side, align, anchor, positioner }) => { return side === 'top' || side === 'bottom' ? anchor.width : anchor.height; }} /> ```

Defaults to 0.

collisionBoundary?any

An element or a rectangle that delimits the area that the popup is confined to.

Defaults to 'clipping-ancestors'.

collisionPadding?any

Additional space to maintain from the edge of the collision boundary.

Defaults to 5.

sticky?boolean

Whether to maintain the popup in the viewport after the anchor element was scrolled out of view.

Defaults to false.

arrowPadding?number

Minimum distance to maintain between the arrow and the edges of the popup. Use it to prevent the arrow element from hanging out of the rounded corners of a popup.

Defaults to 5.

disableAnchorTracking?boolean

Whether to disable the popup from tracking any layout shift of its positioning anchor.

Defaults to false.

collisionAvoidance?CollisionAvoidance

Determines how to handle collisions when positioning the popup. `side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`): - `'flip'`: keep the requested side when it fits; otherwise try the opposite side (`top` and `bottom`, or `left` and `right`). - `'shift'`: never change side; keep the requested side and move the popup within the clipping boundary so it stays visible. - `'none'`: do not correct side-axis overflow. `align` controls overflow on the alignment axis (`start`/`center`/`end`): - `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows. - `'shift'`: keep side and requested alignment, then nudge the popup along the alignment axis to fit. - `'none'`: do not correct alignment-axis overflow. `fallbackAxisSide` controls fallback behavior on the perpendicular axis when the preferred axis cannot fit: - `'start'`: allow perpendicular fallback and try the logical start side first (`top` before `bottom`, or `left` before `right` in LTR). - `'end'`: allow perpendicular fallback and try the logical end side first (`bottom` before `top`, or `right` before `left` in LTR). - `'none'`: do not fallback to the perpendicular axis. When `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`. If `align` is omitted, it defaults to `'flip'`. @example ```jsx <Positioner collisionAvoidance={{ side: 'shift', align: 'shift', fallbackAxisSide: 'none', }} /> ```

Select.ScrollDownArrow

Base UI

Inherited from SelectScrollDownArrowProps

keepMounted?boolean

Whether to keep the HTML element in the DOM while the select popup is not scrollable.

Defaults to false.

Select.ScrollUpArrow

Base UI

Inherited from SelectScrollUpArrowProps

keepMounted?boolean

Whether to keep the HTML element in the DOM while the select popup is not scrollable.

Defaults to false.

Select.Trigger

Base UI
intent?"neutral" | "brand" | "brand-secondary" | "accent" | "danger" | "success" | "warning" | "info" | null
emphasis?"normal" | "subtle" | null
size?"sm" | "md" | "lg" | null

Inherited from SelectTriggerProps

disabled?boolean

Whether the component should ignore user interaction.

Inherited from NonNativeButtonProps

nativeButton?boolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button.

Defaults to false.

Select.Value

Base UI

Inherited from SelectValueProps

children?ReactNode | ((value: any) => ReactNode)

Accepts a function that returns a `ReactNode` to format the selected value. @example ```tsx <Select.Value> {(value: string | null) => value ? labels[value] : 'No value'} </Select.Value> ```

placeholder?ReactNode

The placeholder value to display when no value is selected. This is overridden by `children` if specified, or by a null item's label in `items`.