Pricing

Select handbook

v3.3.0 · @snowui-design-system/components

License: Before reading component documentation, source, or npm usage instructions for the first time, read the full LICENSE. Reuse that review for the same task and version. Public API configuration and internal implementation changes have different license implications. All components use the unified runtime package; source rights remain governed by the license.

Overview

Choose one option from a dropdown. Generate options from data or compose custom options, with search and keyboard support.

When to use it

Use Select for a dropdown single choice. By default, options generates Listbox and MenuItem; explicit children replace that panel. For command-style actions, use action mode.

  • options generates the default list. Explicit Listbox and MenuItem children replace it.

  • persistent stores the selection; action calls onAction and closes without storing it. Generated children use parts; explicit children use their own props.

  • After the list closes with Escape, field and buttonTrigger/SelectButton modes restore focus to the real trigger button. The shared Listbox handles closing from its search header.

Quick start

This example shows the recommended basic composition. See the sections below for more cases and composition limits.

<script lang="ts">	import { Select, Listbox, MenuItem, Text, Button } from '@snowui-design-system/components';</script> <Select placeholder="Select a city" style="width: 20rem; max-width: 100%;">    <Listbox>        <MenuItem as="item" value="shanghai">Shanghai</MenuItem>        <MenuItem as="item" value="hangzhou">Hangzhou</MenuItem>        <MenuItem as="item" value="shenzhen">Shenzhen</MenuItem>    </Listbox></Select>

API

Keyword Type Default Description
gap number — Inherited Frame gap token index. Omit it to retain the original preferred gap. Explicit values override the preference; effective graphics/text spacing remains at least 4 CSS px, including zero, root font scaling and gap scaling.
value SelectValue — Current value, support bind:value
defaultValue SelectValue — Initial value
placeholder string — Placeholder shown in the trigger when no option is selected.
name string — Form submission field name
disabled boolean false Disabled state.
readonly boolean false Read-only status
required boolean false Required status
form string — Specify the external form attribution
autofocus boolean — The first rendering of autofocus
tabindex number — Tab order
title string — Native tooltip.
id string — Native id
invalid boolean false Invalid state at the component level
children Snippet — <Listbox> + <MenuItem> Combination content
options SelectOption[] [] Built-in list data using ListboxItem fields. Explicit children suppress the generated list.
triggerContent Snippet<[SelectTriggerContext]> — Replace the content of the trigger button when buttonTrigger is turned on; receive value, option, label, placeholder, open, disabled.
buttonTrigger boolean false Use Button instead of field triggers; keep the expansion, selection and focus recovery protocols.
buttonBare boolean true It only takes effect in buttonTrigger mode to control whether the built-in Button is shellless bare appearance.
selectionMode "persistent" | "action" "persistent" persistent stores a selected value. action only calls onAction, closes the panel, and restores trigger focus without storing a value.
onAction (value: string) => void — The action mode is called after selecting the option, and the parameter is the string option value; at this time, the panel is closed, and the return value does not participate in the selection.
parts SelectParts — Configure the default Listbox, Input, MenuItem and button triggers; explicit child does not consume parent parts.
ariaLabel string — The accessible name of the trigger; the button trigger mode is also used as a backup display content when there is no customized content.
onclick (event: MouseEvent) => void — The non-disabled and non-readonly trigger is called after clicking to switch the expansion state to receive the original MouseEvent.
oninput (event: Event) => void — The persistent mode calls when a different value is selected to receive the change Event distributed by the hidden form input; the actual new value is read from the binding value, which is not the native text input process.
onchange (event: Event) => void — The persistent mode is called after the selection value is changed, and the same change Event is received; the same value and action mode are not triggered.
onfocus (event: FocusEvent) => void — The field trigger is called when the focus is obtained and receives FocusEvent; the button trigger mode does not use this direct processor.
onblur (event: FocusEvent) => void — The field trigger is called when the focus is lost and receives FocusEvent; the button trigger mode does not use this direct processor.
onkeydown (event: KeyboardEvent) => void — The built-in arrow keys, Enter/Space and Escape of the trigger are called after processing, and KeyboardEvent is received; the processed action cannot be undone.
onkeyup (event: KeyboardEvent) => void — The field trigger is called when the key is released to receive the KeyboardEvent; the button trigger mode does not use this direct processor.

See the base component documentation for inherited capabilities not redeclared here. This table lists only named public Props declared by this component.

Usage boundaries and details

Default built-in mode / explicit combination mode

  • Default built-in mode (recommended): input options, Select automatically generates Listbox and MenuItem; the search box is closed by default, and it is turned on through parts.Listbox.search=true; configure hidden instances through parts.Button, parts.Listbox, parts.Input, parts.MenuItem.

  • When the label or disabled state of an options entry changes for the same value, the selected label, search results, and keyboard options update from the current data. The caller supplies business copy in the target language.

  • Explicit combination mode: explicitly write Listbox / Input / MenuItem, which is used for complex content or item-by-item differences; the subcomponent Props is written directly on the instance.

  • Explicit children replace the built-in Listbox; the two option sets are never active together.

The Listbox in Select defaults to search=false. Use <Listbox search> to open when explicitly combining; use parts={{ Listbox: { search: true } }} to turn on the default built-in mode. The default value of independent Listbox is not affected.

<Select    options={[{ value: "a", label: "A" }]}/>

buttonTrigger replaces the field trigger with a real bare Button. triggerContent takes priority; otherwise it displays the selected item, placeholder, or ariaLabel. Expansion, selection, and focus recovery remain intact.

2026-04-20 Update: New search + custom value support - filterText / customOptions status is held by Select, embedded <Input as="search"> can connect to the filter logic; the embedded Input allowCustomValue prop allows users to add custom options; this attribute does not belong to Select. Combobox has been abandoned.

2026-04-18 Architecture Upgrade: Select v3.0.0 - Listbox is split into independent components, API switches from <option> to <Listbox> + <MenuItem> combination mode, form submission is changed to hidden <input>, no longer relying on hidden <select> + MutationObserver.

2026-04-04 Supplement: Refer to Figma 9996:17631, Select defaults to 12 / 16 padding and 8px gap when used alone; after entering Field, just look at whether there is as="segment". When the segment is not marked, the outer layer Field handles 12/16 and gap = 8, Select removes its own padding, but retains its own gap; when segment is marked, it retains Select own padding and gap, and participates in the dividing line as a segment on the surface of the continuous field.

Inheritance: Reuse Frame’s general state, motion, responsive, Skeleton, and style capabilities. Recommended host: compose with Field for labels, descriptions, and errors.

Field-type native trigger explicitly accesses .snowui-native-control of layout.css, only normalizes box sizing, margin and inherits the font and color of the consumption environment; Select its own font size, line height, Padding, border and status vision do not rely on the optional snowui-base.css. The buttonTrigger mode is assumed by the same host baseline of Button / Frame.

2026-04-27 update: Select v3.1.0 supports direct complex leading and trailing slots; a custom trailing child replaces the default indicator.

Explicit content and default slot

  • Direct Frame as="leading" / Frame as="trailing" children support complex content. For side-by-side graphics and short labels, use singleLine with Text lines={1}; the attachment Frame owns its internal single-line layout and minimum spacing.

  • Select root layout is leading: auto, trigger: minmax(0, 1fr), trailing: auto.

  • A custom direct as="trailing" child replaces the default indicator.

Slot combination boundary

Complex attachments must leave room for editable or selectable content. The following example explicitly sets a minimum of 240 CSS px and uses ScrollArea for local horizontal scrolling in narrower containers.

  • Support direct complex leading / trailing slots.

  • Custom trailing replaces the default indicator.

Form combination and spacing

  • Basic rhythm unification: Field default padding = 12 / 16, gap = 8

  • When Select is used alone: the component itself has 12 / 16 padding and 8 gap

  • After putting it into Field, first see if there is as="segment"

  • No as="segment": the outer layer Field is responsible for 12 / 16 padding and 8 gap; Select removes its own padding, but keeps its own gap

  • There is as="segment": this is a section on the surface of a continuous field; keep Select's own padding and gap

  • There is a dividing line between segment: the horizontal is the left and right dividing line, and the vertical is the upper and lower dividing line.

  • There can only be one dividing line between adjacent segment, and the line cannot be repeated.

  • label / description / error is not responsible for padding, gap and dividing line. They are just content elements.

  • General label + host + error: not used as the basis for the judgment of padding and gap, only responsible for the text content

  • In Field row or Field column, if it is a continuous input segment, the sub-item should be explicitly written as="segment"

Component positioning

Select is a custom drop-down selection box, which adopts Trigger + Listbox combination architecture:

  • Render the trigger and ArrowLineUpDown indicator.

  • Compose <Listbox> and <MenuItem>; Listbox portals itself to body.

  • Communicate with subcomponents through ListboxOwnerContext (setContext)

  • Submit forms through a hidden <input type="hidden"> bridge.

  • Labels, error prompts and continuous field segment logic suggestions are completed through the Field container combination.

  • Remain usable as a standalone field trigger.

Frame ability inheritance

Select is still inherited through the outer layer Frame:

  • responsive / stateStyle / customStates / variants / styles

  • Hover / Click / Focus / whileAppear / timeline / orchestrate

  • loading / skeleton / renderHints

  • as / use / layoutId / itemId / scope / access

Select is a Frame-based custom dropdown, rather than a native <select> element.

Error feedback Shake

Select invalid / aria-invalid defaults to automatically play the error shake. When used alone, it is feedback by Select itself; after entering Field, the embedded state Select does not jitter separately, and the overall feedback is summarized by the outer layer Field.

The error state and shake will not automatically focus or simulate focus-within; the focus state only comes from the user's real focus, explicit autofocus or business code active call focus().

Layout model

Main layout model: slot host layout. See specs/maintainers/LayoutProtocol.md §12.3.

Field mode uses a fixed single-line root with a shrinkable selected value and at least 4 CSS px between graphics and labels, including explicit zero and font/gap scaling. Larger preferred gaps remain. In buttonTrigger mode the actual Button owns content; the invisible positioning host adds no empty spacing. Custom children, popup and form contracts are preserved.

The root layout of Select is fixed according to the three sections of Leading / Trigger / Trailing:

  • The root node is Frame row, and enableAutoLayout is fixed inside the component; the external pass enableAutoLayout={false} does not close the internal slot protocol of Select.

  • Trigger is the Content / Fill slot, which is responsible for displaying the selected value or placeholder, and must maintain the ability of min-width: 0 and text truncation.

  • The default indicator is Trailing / Hug slot.

  • When there is a direct custom as="trailing", the default indicator is hidden, and the last slot is replaced by the custom trailing.

  • Listbox and MenuItem form Select’s dropdown-content protocol; they are outside the root three-slot layout.

Ability boundary

Select is responsible for:

  • Single-choice drop-down selection (Trigger + Listbox combination architecture)

  • value / defaultValue parsing

  • ARIA combobox + listbox complete semantics

  • Keyboard navigation (↑↓ / Enter / Space / Escape / Tab)

  • Submit through the hidden <input> bridge form

  • Drive the sublistbox / MenuItem through ListboxOwnerContext

  • Hold the status of filterText and filteredOptions to provide filtering capabilities for embedded Input as="search"

  • Hold a list of customOptions, which supports users to add/delete custom options at runtime

Select is not responsible for:

  • Business-level option acquisition and remote association

  • Multiple choice mode (extensible in the future)

Usage examples

 <Field>  <Select placeholder="Select a status">    <Listbox>      <MenuItem value="todo">To do</MenuItem>      <MenuItem value="doing">In progress</MenuItem>      <MenuItem value="done">Done</MenuItem>    </Listbox>  </Select></Field>  <Select placeholder="Select a city">  <Listbox>    <Input as="search" placeholder="Search..." />    <MenuItem value="beijing">Beijing</MenuItem>    <MenuItem value="shanghai">Shanghai</MenuItem>    <MenuItem value="hangzhou">Hangzhou</MenuItem>  </Listbox></Select>  <Select placeholder="Select or enter">  <Listbox>    <Input as="search" allowCustomValue placeholder="Search or enter a new option..." />    <MenuItem value="tag-a">Tag A</MenuItem>    <MenuItem value="tag-b">Tag B</MenuItem>  </Listbox></Select>  <Select name="priority" required placeholder="Select priority">  <Listbox>    <MenuItem value="low">Low</MenuItem>    <MenuItem value="medium">Medium</MenuItem>    <MenuItem value="high">High</MenuItem>  </Listbox></Select>  <ScrollArea axis="horizontal" ariaLabel="Select with attachments" width="100%"><Select minWidth={240} placeholder="Tags">  <Frame as="leading" singleLine>    <Icon as="leading" icon={Resource.Tag} />    <Text lines={1}>Tags</Text>  </Frame>  <Frame as="trailing" singleLine>    <Text class="text-black-40" lines={1}>Multi-Select</Text>    <Icon as="trailing" icon={Resource.CaretRight} class="text-black-20" />  </Frame>  <Listbox>    <MenuItem as="item" value="design">Design</MenuItem>    <MenuItem as="item" value="code">Code</MenuItem>  </Listbox></Select></ScrollArea>

Barrier-free and form suggestions

  • Trigger uses role="combobox" + aria-haspopup="listbox" + aria-expanded + aria-controls

  • Listbox uses role="listbox", each option role="option" + aria-selected

  • Keyboard navigation: Arrow Up/Down moves focus, Enter/Space selects, Escape closes, and Tab leaves the menu.

  • If you need error prompts, it is recommended to associate the error nodes in Field through aria-describedby

  • If you need a search pull-down or asynchronous association, you should use the Combobox component.


Select style overlay

Read the full LICENSE first; reuse that review for the same task, version, and scope.

First use public component Props to match the desired appearance, preserving the existing look where possible. If changes are necessary, follow the style selection and override order: use your own class (including Tailwind already in the project) or an instance style. The names below are public overrides actually consumed by source; check their scope and do not modify private DOM. Choose the correct SnowUI token family. External apps need not install SnowUI website utilities.

Variable Purpose and default reference
--snowui-select-background-override Background (see component source for the default).
--snowui-select-font-size-override font-size (see component source for the default).
--snowui-select-gap-override Gap (default var(--gap-2) = 8px)
--snowui-select-height-override Height (see the component source code for the default value)
--snowui-select-indicator-color-override Indicator color (see the component source code for the default value)
--snowui-select-indicator-size-override Indicator size (size px, see the component source code for the default value)
--snowui-select-line-height-override line-height (see component source for the default).
--snowui-select-padding-override Overall padding (see the component source code for the default value)
--snowui-select-placeholder-color-override Placeholder color (see the component source code for the default value)
--snowui-select-radius-override Border-radius (default var(--radius-4) = 16px)
--snowui-select-width-override Width (see the component source code for the default value)

Design Guide · Token · Combination boundary

Select type and status

Required Props: no named required Props.

Two-way binding: bind:value.

Inheritance statement: Omit<FrameProps, "children" | "tag">. The inheritance ability is subject to the public document of the base class; the fields in Omit have been excluded and cannot be used as they are according to the base class.

Share Frame API · Return API

The following type declarations are actually referenced by public Props and come from source. A Svelte Snippet denotes a content fragment; ComponentProps denotes the Props of the corresponding component.

SelectValue

Source: components/src/components/Select.svelte

export type SelectValue = string | number | undefined;

SelectOption

Source: components/src/components/Select.svelte

export type SelectOption = ListboxItem;

ListboxItem

Source: components/src/components/Listbox.svelte

export interface ListboxItem {        value: string;        label: string;        disabled?: boolean;    }

SelectTriggerContext

Source: components/src/components/Select.svelte

export interface SelectTriggerContext {        value: SelectValue;        option: SelectOption | undefined;        label: string | undefined;        placeholder: string | undefined;        open: boolean;        disabled: boolean;    }

SelectParts

Source: components/src/components/Select.svelte

type SelectParts = ComponentPartsOf<{        Listbox: typeof Listbox;        Input: typeof Input;        MenuItem: typeof MenuItem;    }> & {        Button?: Omit<            ComponentProps<typeof Button>,            "children" | "onclick" | "onkeydown" | "disabled"        >;    };

ComponentPartsOf

Source: components/src/logic/component-parts.svelte.ts

export type ComponentPartsOf<    T extends Record<string, Component<any>>,> = {    [K in keyof T]?: ConfigurableComponentPartProps<T[K]>;};

ConfigurableComponentPartProps

Source: components/src/logic/component-parts.svelte.ts

export type ConfigurableComponentPartProps<C extends Component<any>> = Partial<    Omit<ComponentProps<C>, ComponentPartControlledKey>>;

ComponentPartControlledKey

Source: components/src/logic/component-parts.svelte.ts

export type ComponentPartControlledKey =    | "children"    | "as"    | "value"    | "defaultValue"    | "open"    | "selected"    | "checked"    | "disabled"    | "readonly"    | "id"    | `on${string}`    | `aria-${string}`;