Select
A styled native dropdown: `.xoji-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 .xoji-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.
When to use
How this component composes with the rest of the set.
Props
8 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
Variants
default
The standard control: .xoji-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 .xoji-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, with their selectors.
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 .xoji-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 xoji-default register
(derive(xojiDefault, { 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
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.
<xoji-select label="Theme" name="theme" value="auto">
<option value="auto">Match system</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</xoji-select>
<xoji-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>
</xoji-select>
<xoji-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>
</xoji-select>
<script lang="ts">
import { Select } from "@xoji/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 "@xoji/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>