Select
A native <select> with CSS-only chrome. The trigger (closed state) is styled here — sizes, variants, colors, focus ring, disabled state, and a custom chevron. The option popup stays OS-native, which means full keyboard navigation, native mobile pickers, and screen-reader announcement all work without a line of JS.
Install
import "elements-kit/ui/styles/theme.css";import "elements-kit/ui/styles/scaling.css";import "elements-kit/ui/styles/radius.css";import "elements-kit/ui/styles/space.css";import "elements-kit/ui/styles/typography.css";import "elements-kit/ui/styles/cursor.css";import "elements-kit/ui/styles/unset.css";// neutral palette:import "elements-kit/ui/styles/palette/gray.css";import "elements-kit/ui/styles/neutral/gray.css";// any accent scales you want:import "elements-kit/ui/styles/palette/mint.css";import "elements-kit/ui/styles/accent/mint.css";// and the select itself:import "elements-kit/ui/select/select.css";API
<select class="x-select unset" data-variant="surface" data-size="2"> <option value="">Pick one</option> <option value="apple">Apple</option> <option value="orange">Orange</option></select>Pair class="x-select" with class="unset" to strip native chrome (Styles → Unset native styles). Without .unset, browser-default styling will fight the variant rules.
| Attribute | Values |
|---|---|
data-variant | surface (default), soft, text |
data-size | 1, 2 (default), 3 |
data-accent (on element or any ancestor) | any imported color scale — tints soft and text |
disabled (native) | greys out + stops clicks |
Sizing
data-size | height | padding-x | font-size |
|---|---|---|---|
1 | --space-5 | --space-2 | --font-size-1 |
2 (default) | --space-6 | --space-3 | --font-size-2 |
3 | --space-7 | --space-4 | --font-size-3 |
Padding is symmetric (padding-inline). The trigger uses appearance: auto, so the UA sizes it to fit the widest <option> automatically — switching selection doesn’t reflow the layout.
Variants
surface(default) —transparentbackground, thin--neutral-a7inset ring, neutral--neutral-12text. Hover deepens the ring to--neutral-a8. Surface stays neutral even when an ancestor setsdata-accent.soft—--accent-a3accent-tinted track,--accent-12text. Hover bumps the track to--accent-a4. Without an accent,--accent-*falls back to--neutral-*and the select stays gray-tinted.text— transparent, content-box height, negative-margin offset so it sits inline with surrounding body text (same recipe asx-button[data-variant="text"]). Hover gives a subtle--accent-a3wash.
States
:focus-visible—2px--focus-8outline at-1pxoffset (inset).:hover— surface bumps the ring, soft + text deepen the background.:disabled—0.6opacity +pointer-events: none, plus per-variant fallback colors so the disabled state still reads as a control rather than a blank rectangle.
Chevron
The chevron is the browser’s native dropdown indicator (appearance: auto). Visuals vary slightly per browser — Chrome / Firefox draw a small triangle, Safari draws a chunkier double-arrow — but every chevron is correctly positioned, accessibly labelled, and synced with the OS picker. This is the trade-off that buys us correct width sizing (fits widest option) and vertical centering of long option text without CSS hacks.
Accessibility
Native <select> semantics are intact — keyboard navigation, screen-reader announcement, form participation, mobile picker UI, and the option popup all come from the platform. We only style the closed trigger.