Skip to main content
xriptxr

Select

Info:Form
18

A styled native dropdown: .xtyle-control chrome, a custom chevron, and a label, with valid and invalid states across three sizes.

dropdown combobox picker options

Live demo

live · @xtyle/astro

Select

Labelled selects

Sizes

States

Pick a region to continue.

In a form

The chosen value posts once under its name, whether it rendered into light DOM or behind a shadow root. required is real constraint validity, so the browser blocks a submit with no plan chosen.

Nothing submitted yet.

Select is a thin, accessible skin over the native <select>. It inherits the shared .xtyle-control chrome (the same fill, border, radius, and focus ring as Field) so it sits flush beside other form controls, then hides the platform arrow and paints its own chevron, colored by focus, invalid, and disabled state.

Options are plain <option> / <optgroup> children passed straight through to the native element, so keyboard navigation, type-ahead, and the OS picker all come for free. A label wires an accessible name, invalid plus error surface validation, and sm / md / lg tune the density. It is form-associated: give it a name and the chosen value posts once, whether the element rendered into light DOM or behind a shadow root; required and invalid reach the form as real constraint validity rather than styling alone.

When to use

How this component composes with the rest of the set.

Inherits .xtyle-control chrome from the base layer, so it lines up pixel-for-pixel with Field and Textarea.
Pair with Field for text inputs and Button for the submit action to build a complete form row.
Options are native <option> / <optgroup> elements. No custom item component required.

Props

11 props, straight from the manifest.

PropTypeDefaultBindingsDescription
label string —
html svelte astro
Visible label and accessible name. When empty, falls back to aria-label.
value string —
html svelte astro
The selected option's value. Two-way bindable in Svelte.
size Size
sm md lg
md
html svelte astro
Control density.
name string —
html svelte astro
Form field name, forwarded to the native select for submission.
invalid boolean false
html svelte astro
Marks the field invalid: danger border, danger chevron, and aria-invalid.
error string —
html svelte astro
Validation message shown beneath the control; wired via aria-describedby when invalid.
focusable boolean true
html svelte astro
Set false to keep the control out of sequential focus navigation, for an app driving selection from its own keyboard cursor. The host's own tabindex cannot express this, because focus lands on the inner control rather than the host, and setting it there from outside does not survive the next render.
cursor boolean false
html svelte astro
Marks the select as the one an app-owned keyboard cursor rests on, and draws exactly the :focus-visible ring (tinted chevron, invalid tint and forced-colors outline included) without moving DOM focus. For a settings list driven from the app's own input layer, usually alongside focusable={false}. Controlled: move it in your own input handler. Changes no ARIA.
disabled boolean false
html svelte astro
Disables the native select and mutes the chevron.
required boolean false
html svelte astro
Marks the field required, reflecting required and aria-required onto the native select.
requiredMessage html: required-message string Please select a value.
html svelte astro
The message the browser shows when required is unmet. The platform localizes its own constraint messages and this one is xtyle's, so an app that is not in English should set it.

Events

What the component emits, and what rides along on event.detail. The name in the first column is the one addEventListener takes.

EventDetailBindingsDescription
change { value }
html svelte astro
The chosen option changed.

Appearance

Variants

default

.xtyle-select

The standard control: .xtyle-control chrome with the accent focus ring.

invalid

.xtyle-select--invalid

Validation-failed treatment: danger border and chevron, danger-tinted focus ring.

Sizes

sm

.xtyle-select--sm

Compact.

md

default
.xtyle-select

Default.

lg

.xtyle-select--lg

Large.

States

focus-visible

.xtyle-select__field:focus-visible + .xtyle-select__chevron

Keyboard focus: the .xtyle-control accent ring, with the chevron tinted to match.

invalid-focus

.xtyle-select--invalid .xtyle-select__field:focus-visible

Focus while invalid: the danger border holds and the ring shifts to the danger tint.

cursor

.xtyle-select__field[data-cursor] + .xtyle-select__chevron

App-owned keyboard cursor: cursor puts data-cursor on the native select, which shares the .xtyle-control focus-visible rule and the chevron tint, so it draws the same ring and the same forced-colors outline while DOM focus stays where it is.

disabled

.xtyle-select__field:disabled + .xtyle-select__chevron

Non-interactive: the native control mutes and the chevron drops to the disabled ink.

Anatomy

The named parts that make up the component. Created from script it renders a shadow root, where a ::part() handle is reachable from your own stylesheet and the class beside it is internal. Upgraded over server-rendered markup it renders light DOM, where the class is the handle.

root

Success:::part(root) .xtyle-select

The vertical stack holding the label, control, and error message.

--space-1 --font-sans

label

Success:::part(label) .xtyle-select__label

The control's visible label, hidden when no label is set.

--text-sm --weight-medium --fg-1 --leading-normal

control

Success:::part(control) .xtyle-select__control

The positioning wrapper that overlays the chevron on the native select.

select

Success:::part(select) .xtyle-select__field

The native <select> carrying the shared .xtyle-control chrome with the platform arrow suppressed.

--space-7

chevron

Success:::part(chevron) .xtyle-select__chevron

The custom dropdown indicator, decorative and pointer-transparent, recolored by state.

--space-3 --fg-2 --accent --duration-fast --ease-standard

error

Success:::part(error) .xtyle-select__error

The validation message shown when invalid and an error string are set.

--text-sm --leading-normal --danger

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 18/18 consumed tokens produced default register: 311 tokens

Live coverage check against the xtyle-default register (derive(xtyleDefault, { anchors }) → coverComponent(manifest, register)). Every token this component consumes must be a key the algorithm produces.

--accent --border-normal --danger --danger-bg --danger-text --duration-fast --ease-standard --fg-1 --fg-2 --fg-disabled --font-sans --leading-normal --space-1 --space-3 --space-7 --text-lg --text-sm --weight-medium

Slots

default
html svelte astro

The <option> and <optgroup> elements, passed straight through to the native select.

Accessibility

Renders a native <select>, so keyboard navigation, type-ahead, and the OS picker work without any custom scripting.
label provides the accessible name via a for-linked <label>; when absent, an aria-label is required and the html binding warns at runtime when neither is present.
The host's aria-label / aria-labelledby names the inner <select>, ahead of label; aria-labelledby ids are read from the host's own tree.
invalid sets aria-invalid="true"; when an error string is present it is linked with aria-describedby.
required reflects both the native required attribute and aria-required for assistive tech.
The chevron is decorative (aria-hidden); selection state lives in the native control, not the visual.
Focus is shown with the shared .xtyle-control ring plus a transparent outline that the forced-colors base rule promotes to a real system outline.
cursor is visual only and sets no ARIA: an app-owned cursor is not focus, so it is not announced as focus, and the chosen option stays in the native control alone. An app that needs the cursor position announced owns that channel itself, typically by moving real focus or with a live region.

Code

Labels, groups, and validation

A plain labelled select, a grouped large select, and an invalid required select with an error message.

<xtyle-select label="Theme" name="theme" value="auto">
	<option value="auto">Match system</option>
	<option value="light">Light</option>
	<option value="dark">Dark</option>
</xtyle-select>

<xtyle-select label="Plan" size="lg">
	<optgroup label="Personal">
		<option value="free">Free</option>
		<option value="pro">Pro</option>
	</optgroup>
	<optgroup label="Teams">
		<option value="team">Team</option>
		<option value="org">Organization</option>
	</optgroup>
</xtyle-select>

<xtyle-select label="Country" invalid error="Select a country to continue." required>
	<option value="" disabled selected>Choose…</option>
	<option value="us">United States</option>
	<option value="ca">Canada</option>
</xtyle-select>

In a form

A required select inside a <form> with a submit button: the chosen value posts under its name, and leaving it empty blocks the submit.

<form>
	<xtyle-select label="Plan" name="plan" required>
		<option value="" selected>Choose…</option>
		<option value="team">Team</option>
		<option value="pro">Pro</option>
	</xtyle-select>
	<xtyle-button type="submit">Subscribe</xtyle-button>
</form>