Select
A styled native dropdown: .xtyle-control chrome, a custom chevron, and a label, with valid and invalid states across three sizes.
Live demo
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.
Props
10 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
aria-label. | ||||
aria-invalid. | ||||
aria-describedby when invalid. | ||||
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. | ||||
required and aria-required onto the native select. | ||||
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.
| Event | Detail | Bindings | Description |
|---|---|---|---|
Appearance
Variants
default
The standard control: .xtyle-control chrome with the accent focus ring.
invalid
Validation-failed treatment: danger border and chevron, danger-tinted focus ring.
Sizes
sm
Compact.
md
Default.
lg
Large.
States
focus-visible
Keyboard focus: the .xtyle-control accent ring, with the chevron tinted to match.
invalid-focus
Focus while invalid: the danger border holds and the ring shifts to the danger tint.
disabled
Non-interactive: the native control mutes and the chevron drops to the disabled ink.
Anatomy
The named parts that make up the component. A ::part() handle is reachable from your own stylesheet; the class beside it is the component's internal selector, which a shadow boundary keeps to itself.
root
The vertical stack holding the label, control, and error message.
label
The control's visible label, hidden when no label is set.
control
The positioning wrapper that overlays the chevron on the native select.
select
The native <select> carrying the shared .xtyle-control chrome with the platform arrow suppressed.
chevron
The custom dropdown indicator, decorative and pointer-transparent, recolored by state.
error
The validation message shown when invalid and an error string are set.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
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-mediumSlots
The <option> and <optgroup> elements, passed straight through to the native select.
Accessibility
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><script lang="ts">
import { Select } from "@xtyle/svelte";
let theme = $state("auto");
</script>
<Select label="Theme" name="theme" bind:value={theme}>
<option value="auto">Match system</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</Select>
<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>
</Select>
<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>
</Select>---
import { Select } from "@xtyle/astro";
---
<Select label="Theme" name="theme" value="auto">
<option value="auto">Match system</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</Select>
<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>
</Select>
<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>
</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><script lang="ts">
import { Select, Button } from "@xtyle/svelte";
let plan = $state("");
</script>
<form onsubmit={(event) => { event.preventDefault(); console.log(new FormData(event.currentTarget)); }}>
<Select label="Plan" name="plan" required bind:value={plan}>
<option value="">Choose…</option>
<option value="team">Team</option>
<option value="pro">Pro</option>
</Select>
<Button type="submit">Subscribe</Button>
</form>---
import Select from "@xtyle/astro/Select.astro";
import Button from "@xtyle/astro/Button.astro";
---
<form>
<Select label="Plan" name="plan" required>
<option value="" selected>Choose…</option>
<option value="team">Team</option>
<option value="pro">Pro</option>
</Select>
<Button type="submit">Subscribe</Button>
</form>