Select
A custom dropdown, not a native select — the trigger matches Input exactly and the panel is real markup. Full keyboard support, type-ahead, and dismissal on Escape or outside click.
src/components/ui/select.tsxAbout
From the component's own source, where it was written.
A custom dropdown, not a native <select>: the trigger matches Input exactly, and the panel is real markup that can nest.
An option with children becomes a submenu, and those nest without limit. Each level owns its own open state and highlight rather than flattening into one index — a flat model cannot express "the third item of the second submenu is focused while its parent row stays highlighted".
Install
npx astralyx-ui add select
Usage
import { Select } from '@/components/ui/select'
const options = [
{ value: 'system', label: 'System' },
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
]
<Select options={options} defaultValue="system" />Composer
Change the props and the source below follows.
<Select
options={options}
placeholder="Select a theme"
/>Examples
Nested submenus
const options = [
{ value: 'new', label: 'New file' },
{
value: 'export',
label: 'Export as',
children: [
{ value: 'image', label: 'Image', children: [
{ value: 'png', label: 'PNG' },
{ value: 'jpg', label: 'JPEG' },
]},
{ value: 'svg', label: 'SVG' },
],
},
]
<Select options={options} placeholder="Actions" />Variants
<Select options={options} defaultValue="system" />
<Select options={options} variant="secondary" />
<Select options={options} variant="ghost" />Sizes
<Select options={options} size="xs" />
<Select options={options} size="default" />
<Select options={options} size="xl" />With icon, and a disabled option
<Select options={options} icon={<Globe />} placeholder="Region" />States
<Select options={options} error />
<Select options={options} disabled />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
Select
Extends Omit<ComponentProps<'div'>, 'onChange' | 'defaultValue'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| error | boolean | false | Red border. Also sets aria-invalid. |
| variant | 'default' | 'secondary' | 'ghost' | 'default' | Matches Input, so the two line up in a form. |
| size | 'sm' | 'default' | 'xs' | 'lg' | 'xl' | 'default' | Same size steps as Input. |
| options | SelectOption[] | required | The choices. A disabled option is skipped by arrow keys and type-ahead. |
| value | string | — | Controlled and uncontrolled selection. |
| defaultValue | string | — | Controlled and uncontrolled selection. |
| onValueChange | (value: string) => void | — | Fires with the chosen value. |
| placeholder | string | 'Select…' | Shown until something is chosen. |
| disabled | boolean | false | — |
| icon | ReactNode | — | Leading adornment in the trigger. |
| triggerClassName | string | — | — |
| triggerLabel | string | — | Accessible name for the trigger. |
| emptyMessage | ReactNode | 'No options' | Shown when options is empty. |
Functions
| Function | Signature | Description |
|---|---|---|
| selectVariants | (props?: { variant?: 'default' | 'secondary' | 'ghost'; size?: 'sm' | 'default' | 'xs' | 'lg' | 'xl'; error?: boolean }) => string | — |
Types
SelectOption
A custom dropdown, not a native <select>: the trigger matches Input exactly, and the panel is real markup that can nest.
| Field | Type | Description |
|---|---|---|
| value | string | — |
| label | string | — |
| disabled? | boolean | — |
| children? | SelectOption[] | Makes an option a submenu instead of a value, and nests without limit. The row shows a chevron and cannot be selected itself. |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| placement | right → left → top | A submenu opens to the right, falls back to the left, then above. Fixed positioning, so it is never clipped by a scrolling ancestor. |
| keyboard | arrows | Right or Enter opens a submenu and focuses it, Left closes one level and returns focus to the parent row, Escape closes. Type-ahead searches the level you are in. |