Select
Custom dropdown select with keyboard navigation, option groups, icons, descriptions, clearable state, and validation.
import { Select } from 'phoundry-ui'; Control
Props that affect the trigger button appearance and behavior.
Basic
Selected: apple
Show code
const options = [
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
{ value: 'cherry', label: 'Cherry' },
];
<Select options={options} value={selected} onchange={(v) => selected = v} />Variants
outline
ghost (minimal)
filled (default)
Show code
<Select options={opts} value={v} onchange={set} variant="outline" />
<Select options={opts} value={v} onchange={set} variant="ghost" />
<Select options={opts} value={v} onchange={set} variant="filled" />Sizes
sm
md (default)
Show code
<Select options={opts} value={v} onchange={set} size="sm" />
<Select options={opts} value={v} onchange={set} size="md" />Shrink
Fits the trigger width to its content instead of stretching full-width.
Show code
<Select options={opts} value={v} onchange={set} shrink />Clearable
Show code
<Select options={opts} value={v} onchange={set} clearable />Disabled
Show code
<Select options={opts} value={v} onchange={set} disabled />Validation
Selection required
Show code
<Select options={opts} value={v} onchange={set} invalid error="Selection required" />Selected indicator
selectedIndicator="check" shows a checkmark instead of tinting the active row (helpful when accent-on-background is too strong).
Show code
<Select
options={opts}
value={v}
onchange={set}
selectedIndicator="check"
/>Dropdown
Options support icons, descriptions, and groups for richer dropdowns.
With Icons
Selected: none
Show code
const options = [
{ value: 'svelte', label: 'Svelte', icon: 'logos:svelte-icon' },
{ value: 'react', label: 'React', icon: 'logos:react' },
{ value: 'vue', label: 'Vue', icon: 'logos:vue' },
];
<Select options={options} value={v} onchange={set} />With Descriptions
Selected: none
Show code
const options = [
{ value: 'sm', label: 'Small', description: '640px max width' },
{ value: 'md', label: 'Medium', description: '768px max width' },
{ value: 'lg', label: 'Large', description: '1024px max width' },
];
<Select options={options} value={v} onchange={set} />Grouped Options
Selected: none
Show code
const options = [
{
label: 'Frontend',
options: [
{ value: 'svelte', label: 'Svelte' },
{ value: 'react', label: 'React' },
],
},
{
label: 'Backend',
options: [
{ value: 'rust', label: 'Rust' },
{ value: 'go', label: 'Go' },
],
},
];
<Select options={options} value={v} onchange={set} />Custom Snippet
Pass a custom object with a snippet for fully custom rendering in both the dropdown and the trigger. Set selectable: false to embed interactive
content that doesn't trigger selection.
Selected: none
Show code
import { Badge } from 'phoundry-ui';
{#snippet statusRow({ option, selected })}
<span class="flex items-center gap-2 flex-1 min-w-0">
<Badge variant={variants[option.value]} dot />
<span class="text-xs truncate">{option.label}</span>
</span>
{/snippet}
{#snippet customInput()}
<TextInput
value={customStatusText}
oninput={(v) => customStatusText = v}
placeholder="Custom status..."
size="sm"
variant="fill"
/>
{/snippet}
const options = [
{ value: 'online', label: 'Online', custom: { snippet: statusRow } },
{ value: 'away', label: 'Away', custom: { snippet: statusRow } },
{ value: 'busy', label: 'Busy', custom: { snippet: statusRow } },
// Non-selectable row with interactive content
{ value: '', label: '', custom: { snippet: customInput, selectable: false } },
];
<Select options={options} value={v} onchange={set} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| options required | OptionOrGroup[] | — | Flat options or option groups to display |
| value required | T | undefined | — | Currently selected value |
| onchange required | (value: T | undefined) => void | — | Called when selection changes |
| placeholder | string | 'Select...' | Placeholder text when no value is selected |
| size | 'sm' | 'md' | 'md' | Trigger height aligned to Button `sm` / `md` control scale |
| disabled | boolean | false | Disables the select |
| variant | 'outline' | 'ghost' | 'filled' | 'filled' | Visual style of the trigger (`ghost` is the minimal inline style). |
| shrink | boolean | false | When true, trigger width fits content instead of full width |
| clearable | boolean | false | Shows a clear button when a value is selected |
| required | boolean | false | Reserved for HTML semantics — not wired to DOM attributes yet; use `invalid` / `error` for validation UX. |
| invalid | boolean | false | Applies error styling to the trigger |
| error | string | — | Error message displayed below the select |
| id | string | — | HTML id attribute |
| selectedIndicator | 'check' | 'background' | 'background' | How the selected option is indicated in the dropdown |
| name | string | — | Hidden input name for form submission |
| element | HTMLButtonElement | — | Bindable reference to the trigger button |
SelectOption
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value required | T | — | The value associated with this option |
| label required | string | — | Display text for the option (also used as accessible fallback) |
| description | string | — | Secondary text shown below the label |
| icon | string | — | Iconify icon string shown before the label |
| custom | SelectOptionCustom | — | Custom rendering config (see below) |
SelectOptionCustom
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| snippet required | Snippet<[{ option, selected }]> | — | Svelte snippet that replaces the default option content |
| selectable | boolean | true | When false, the option row is non-selectable (renders as a div for interactive content) |
SelectOptionGroup
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label required | string | — | Group header text |
| options required | SelectOption[] | — | Options within this group |
Usage tips
- Use
variant="ghost"when the select is inline within a toolbar or header. - Pass
shrinkto prevent the trigger from stretching to full width. - Combine
invalid+errorfor form validation; the error renders below the trigger. - The dropdown renders as a portal-based overlay for correct z-index stacking.
- Full keyboard navigation: Arrow keys, Enter/Space to select, Escape to close.
- Pass
nameto emit a hidden input for plain HTML forms alongside the visible button trigger.