Phoundry UI

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

PropTypeDefaultDescription
options requiredOptionOrGroup[]-Flat options or option groups to display
value requiredT | 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 booleanfalseDisables the select
variant 'outline' | 'ghost' | 'filled''filled'Visual style of the trigger (`ghost` is the minimal inline style).
shrink booleanfalseWhen true, trigger width fits content instead of full width
clearable booleanfalseShows a clear button when a value is selected
required booleanfalseReserved for HTML semantics - not wired to DOM attributes yet; use `invalid` / `error` for validation UX.
invalid booleanfalseApplies 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

PropTypeDefaultDescription
value requiredT-The value associated with this option
label requiredstring-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

PropTypeDefaultDescription
snippet requiredSnippet<[{ option, selected }]>-Svelte snippet that replaces the default option content
selectable booleantrueWhen false, the option row is non-selectable (renders as a div for interactive content)

SelectOptionGroup

Props

PropTypeDefaultDescription
label requiredstring-Group header text
options requiredSelectOption[]-Options within this group

Usage tips

  • Use variant="ghost" when the select is inline within a toolbar or header.
  • Pass shrink to prevent the trigger from stretching to full width.
  • Combine invalid + error for 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 name to emit a hidden input for plain HTML forms alongside the visible button trigger.