InputWithSelect from @prepared911/ui-core pairs a text input with an adjacent unit/scope select (currency code next to an amount, phone country next to a number). The components wire their own semantics; your job is to label the pair as one, keep tab order logical, and avoid double-announcing the value.
What the component handles
- Input semantics. The text input renders as a standard
<input> with label association.
- Select semantics. The paired
Select renders as a Radix combobox with role="combobox" and a listbox popover.
- Focus order. Lead-slot selects are reached before the input; tail-slot selects after.
- A unified label. Use a single visible label on the input (e.g. "Amount") and an accessible name on the select that identifies its role ("Currency"). Don't leave either control unnamed.
- Sensible tab order. When the select refines the input's meaning (unit, currency), keep the select before the input so users choose context before typing the value.
- Programmatic error state. Pass
aria-invalid and aria-errormessage on the input; when the error is about the paired select (unsupported currency), indicate which control is in error.
- Tab / Shift+Tab move between the select and input in their visual order.
- Arrow keys open and navigate the select's listbox once focused.
- Enter commits the input; Escape dismisses the select popover.
- Input →
<input> with aria-describedby for helper text.
- Select →
role="combobox" with aria-expanded, aria-controls, and a linked listbox.
- Both controls share the same field label via the visible copy above them.
- Redundant announcements. If the select's selected value already appears in the input's label, the rotor can read it twice. Keep the select label distinct ("Currency") and the input label scoped to the value ("Amount").
- Dependent validation. When the input's validity depends on the select (e.g. currency-specific decimal places), update
aria-invalid and the error text immediately after the select changes, not only after the input blurs.