Skip to main content

Select

An accessible single-select dropdown component built on the WAI-ARIA listbox pattern.
Use it when you need a fully keyboard-navigable, screen-reader friendly alternative to a native <select>.


Usage

Basic usage

import { Select } from '@nofinite/nui';

<Select
options={[
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
{ value: 'orange', label: 'Orange' },
]}
/>;

Controlled usage

const [value, setValue] = useState('apple');

<Select
value={value}
onChange={(v) => setValue(v)}
options={[
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
]}
/>;

Uncontrolled usage

<Select
defaultValue="b"
options={[
{ value: 'a', label: 'Apple' },
{ value: 'b', label: 'Banana' },
{ value: 'c', label: 'Cherry' },
]}
/>

Variants

This component does not expose explicit visual variants. Styling is theme-driven via CSS variables and supports light/dark themes.

<div data-theme="dark">
<Select options={[{ value: '1', label: 'Option A' }]} />
</div>

Guidelines:

  • Use themes instead of variants for consistent design.
  • Avoid creating multiple Select styles with custom CSS overrides.

Sizes

The Select does not expose size props.

  • Width is determined by content or container.
  • Minimum width is 200px by default.
  • Use layout styles for sizing.
<Select style={{ width: 300 }} />

States

Disabled

<Select disabled options={[{ value: 'a', label: 'Apple' }]} />
  • Trigger cannot be opened.
  • Keyboard and mouse interactions are blocked.
  • Proper aria-disabled is applied.

Disabled options

<Select
options={[
{ value: 'card', label: 'Credit Card' },
{ value: 'cod', label: 'Cash on Delivery', disabled: true },
]}
/>
  • Disabled options are skipped during keyboard navigation.
  • Cannot be selected via mouse or keyboard.

Native Props / Composition

The Select trigger is rendered as a <button> and forwards relevant attributes.

<Select
id="payment-method"
className="my-select"
aria-label="Select payment method"
options={[{ value: 'upi', label: 'UPI' }]}
/>

Form compatibility

If name is provided, a hidden <input> is rendered for form submissions.

<Select
name="fruit"
options={[
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
]}
/>

Props

PropTypeDefaultDescription
options{ value: string; label: ReactNode; disabled?: boolean }[]Options rendered in the listbox
valuestringControlled selected value
defaultValuestringInitial value for uncontrolled usage
onChange(value: string) => voidCalled when selection changes
placeholderstring"Select..."Shown when no value is selected
disabledbooleanfalseDisables the Select
namestringAdds hidden input for forms
idstringauto-generatedBase id for ARIA relationships
classNamestring""Additional wrapper class

Behavior Notes

  • Supports controlled and uncontrolled modes.
  • Uses a portal to render the listbox.
  • Automatically restores focus to the trigger on close.
  • Clicking outside closes the listbox.
  • Active option is managed internally (DOM focus remains on listbox).

Keyboard support

  • ArrowUp / ArrowDown – move active option
  • Home / End – jump to first / last option
  • Enter / Space – select active option
  • Esc – close listbox
  • Typeahead – type letters to jump to matching options

Accessibility

  • Implements the WAI-ARIA listbox pattern
  • Trigger: <button aria-haspopup="listbox">
  • Listbox: <div role="listbox">
  • Options: <div role="option">
  • Uses aria-selected, aria-disabled, and aria-labelledby
  • Fully keyboard navigable
  • Screen-reader friendly selection announcements

Example:

<Select
aria-label="Choose a fruit"
options={[{ value: 'a', label: 'Apple' }]}
/>

Layout

  • Inline-block component by default
  • Listbox is positioned relative to trigger using fixed positioning
  • Width matches trigger minimum width
<div style={{ width: 320 }}>
<Select options={[{ value: 'x', label: 'Wide Select' }]} />
</div>

Best Practices

Do

  • Use for single selection only.
  • Provide clear labels for options.
  • Use name when integrating with forms.
  • Keep option labels short and readable.

Don’t

  • Use for multi-select scenarios.
  • Disable all options.
  • Nest complex interactive elements inside option labels.
  • Override ARIA attributes manually.