Select
A labeled native select with flat options, option groups, a placeholder option, and standard field accessibility.
Import
import { Select } from "@hyzer-labs/ui"Demo
Select vs Combobox
Select when your option set is small and static: single-select, or
multi-select via the native multiple attribute. Use Combobox when there are many options (where filtering or virtualization
helps), or when you need search and type-to-filter.The placeholder is a disabled leading option, so it cannot be re-selected once a real choice is made. Disabled options stay visible but inert.
const putters: SelectOption[] = [
{ value: 'aviar', label: 'Aviar' },
{ value: 'luna', label: 'Luna' },
{ value: 'judge', label: 'Judge' },
{ value: 'zone', label: 'Zone (out of stock)', disabled: true }
];
<Select name="putter" label="Putter" options={putters} bind:value={putter} placeholder="Pick a putter" />An entry with group renders an <optgroup>. Flat options
and groups can mix in the same array; everything renders in array order.
const bag: SelectOption[] = [
{ group: 'Drivers', options: [{ value: 'destroyer', label: 'Destroyer' }, …] },
{ group: 'Midranges', options: [{ value: 'buzzz', label: 'Buzzz' }, …] },
{ group: 'Putters', options: [{ value: 'aviar', label: 'Aviar' }, …] }
];
<Select name="disc" label="Disc" options={bag} placeholder="Pick a disc" />The multiple prop renders the native multiple attribute and
switches value to a string[]. There is no placeholder option
in this mode. The single <select multiple> element submits repeated name values, so new FormData(form).getAll(name) returns each selected
value in option order.
Bound value: destroyer
let discs = $state<string[]>([]);
<form onsubmit={handleSubmit}>
<Select name="discs-demo" label="Discs" options={discOptions} multiple bind:value={discs} />
<button type="submit">Submit</button>
</form>
// handleSubmit reads: new FormData(form).getAll("discs-demo")description and error are announced with the field: both chain
into aria-describedby. Error and disabled are also field states. The
wrapper's data-state reflects them, and error wins.
Long tees add roughly 1,200 feet to the round.
Pick a division to register.
<Select
name="tees"
label="Tee position"
options={tees}
description="Long tees add roughly 1,200 feet to the round."
/>
<Select name="division" label="Division" options={divisions} error="Pick a division to register." />
<Select name="division2" label="Division" options={divisions} required />
<Select name="card" label="Card" options={tees} disabled />Props
| Name | Type | Default | Note |
|---|---|---|---|
name | string | — | Required. Form field name. |
label | string | — | Required. Always in the DOM; sr-only with hideLabel. |
options | SelectOption[] | — | Required. Flat options and optgroups mix freely — see SelectOption below. |
multiple | boolean | false | Renders the native multiple attribute; switches value to string[]. |
value | string | string[] | '' (single) / [] (multiple) | Bindable. A discriminated union on multiple: string when omitted/false, string[] when true. |
placeholder | string | 'Select...' | Rendered as a disabled leading option. |
description | string | — | Help text below the label. |
error | string | — | Inline error message; sets the error state. |
required | boolean | false | |
disabled | boolean | false | |
hideLabel | boolean | false | |
class | string | — | Merged after the hz-field class. |
SelectOption (flat arm: FormOption)
| Name | Type | Default | Note |
|---|---|---|---|
value | string | — | Required. |
label | string | — | Required. |
disabled | boolean | — | — |
SelectOption (group arm)
| Name | Type | Default | Note |
|---|---|---|---|
group | string | — | Group form: renders an <optgroup> with this label. |
options | FormOption[] | — | Group form: the flat options inside the group. |
Theme hooks
What this component promises your CSS. The reference theme styles exactly these — from @layer hz-theme, so your unlayered rules win. See Styling Components for the how.
Root class: .hz-field
Data attributes
| Hook | Values | Styles |
|---|---|---|
data-state | 'default' | 'error' | 'disabled' | The universal field state hook, on the root. Precedence is fixed: error beats disabled. |
Part classes
| Hook | Values | Styles |
|---|---|---|
.hz-field-label | child element | The label. |
.hz-field-required | child element | The required marker. |
.hz-field-description | child element | The hint text. |
.hz-field-error | child element | The error message. |
Accessibility
The select is associated with its label via id/for. With hideLabel, the label stays in the DOM as screen-reader-only text.
description and error chain into aria-describedby, description first. required sets aria-required, and an error sets aria-invalid.
The control is the native <select>, so keyboard and assistive-tech behavior come from the platform.
References: MDN: <select>