Inputs
Select
A native select with a fluid chevron, accessible and zero-JS. Compose it inside a Field for its label, description and error.
Import
import { Field, Select } from "@loamui/core";Usage
Every example has a CSS tab. That’s the real, complete stylesheet for the component: plain, static CSS, with nothing running in the browser.
Basic usage
Inside Field.Root the select reads its id from the field, so Field.Label is wired without any props.
Starting unanswered
An unanswered start is an option like any other: a first child with an empty value, disabled so it can never be chosen. The select starts on it, so a required field the user skipped is caught, and the prompt reads muted until answered.
Groups and disabled options
Options pass straight to the native select, so optgroup and disabled work exactly as the platform defines them.
With a description
Field.Description links to the select via aria-describedby, the same wiring every control gets inside a Field.
Where you are resident for tax.
Error state
A Field.Error before the control marks the field invalid and is announced: the message's presence is the state.
Error: Select a country
Guidance
When to use it
- For choosing one option from a longer list (roughly 5+) where showing them all would take too much space.
- When the options are familiar and the user doesn't need to compare them side by side.
When not to
- For a small set of options the user should see at once: use Radio, which shows every choice up front.
- For yes/no or on/off: use Checkbox or Switch.
- For unrestricted text entry: use Input.
Accessibility
- Wraps a native <select>, so keyboard interaction, typeahead and the mobile picker come from the platform.
- Inside a Field.Root it self-wires: the label, description and error are linked via id / aria-describedby / aria-invalid, with the error announced as role="alert". See the Field page.
- A prompt is a disabled first option with an empty value, so it is never a selectable value; the select starts on it when nothing else is chosen.
- Under forced colours the danger border colour is dropped, so an invalid select carries its state as an outline in a system colour, with the focus ring offset further out.
How it works
Start without a value
Make the first child a disabled option with an empty value, so the field starts unanswered and required validation catches an untouched select. With no such option the first real one is pre-selected, and users who skip the field silently submit an answer they never chose. Write the prompt as a native option element.
Order the options
List options alphabetically so users can predict where an answer sits in a long menu, the reason to use a Select at all. Depart only for an order that is genuinely more useful in the domain, like months in calendar order or years newest-first.
A select conceals its options
Until opened, the menu shows one value and hides every alternative, so users can't survey or compare the choices. The cost shows up in usability testing: people try to type into the closed control, mistake the focused option for a selected one, and struggle to operate the menu zoomed in. That is the cost that makes RadioGroup the better control for small sets. Reserve Select for long lists of familiar answers users recognise rather than weigh up.
Error messages
Say what happened and how to fix it, in the words of the question itself. See the writing guidance on the Field page.
| Situation | Message |
|---|---|
| Nothing is selected | Select [whatever the label asks for] |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Native <option> / <optgroup> elements, passed straight through. A leading <option value="" disabled> is the unanswered start. |
wrapperProps | PartProps<"div"> | — | Props for the box around the select, which positions the chevron. className, style, ref and every other prop land on the <select> itself; this is the one way to reach the box. |
...others | SelectHTMLAttributes | — | All native <select> props, and ref, are forwarded to the <select>, except size (a listbox is not this component). |