Forms
The field contract
Every input in the kit takes the same four presentation props, and wires them to the control with generated ids so the accessible name and description are correct:
<TextInput
label="Email"
description="We only use this for receipts."
error={errors.email}
required
/>error is a node, not a boolean: pass the message and the field colours itself
($red9 border, red message) and announces. Pass undefined when valid.
The family
| Component | For |
|---|---|
TextInput | single-line text |
Textarea | multi-line, with minRows/maxRows |
PasswordInput | text with a reveal toggle |
NumberInput | numeric, with steppers and clamping |
MaskInput | formatted input (phone, card) |
PinInput | one-time codes |
Select · MultiSelect | choose from a list |
Autocomplete · Combobox | filter as you type |
TagsInput | free-form chips |
Checkbox · Radio · Switch | booleans and choices |
Slider · Rating | ranges |
FileInput · FileButton | files |
ColorInput · ColorPicker | colours |
DateInput · TimeInput | dates and times |
InputBase is the shared shell if you need to put a custom control inside a
kit-shaped field.
Controlled and uncontrolled
Every input supports both, through the same
useUncontrolled hook internally:
<TextInput defaultValue="hello" /> // uncontrolled
<TextInput value={value} onChangeText={setValue} /> // controlledPass value or defaultValue, never both.
Layout
<Stack gap="$md">
<TextInput label="Name" />
<Group grow>
<TextInput label="City" />
<TextInput label="Postcode" />
</Group>
<Fieldset legend="Delivery">
<Radio.Group value={speed} onChange={setSpeed}>
<Radio value="standard" label="Standard" />
<Radio value="express" label="Express" />
</Radio.Group>
</Fieldset>
<Group justify="flex-end">
<Button variant="subtle">Cancel</Button>
<Button loading={saving}>Save</Button>
</Group>
</Stack>Fieldset gives you a real <fieldset>/<legend> on the web, which screen readers
use to group the controls.
Validation
The kit does not ship a form library — use React Hook Form, TanStack Form, or your own state. What it gives you is the presentation contract above. With React Hook Form:
<Controller
control={control}
name="email"
render={({ field, fieldState }) => (
<TextInput
label="Email"
value={field.value}
onChangeText={field.onChange}
onBlur={field.onBlur}
error={fieldState.error?.message}
/>
)}
/>useValidatedState covers the simple case
without a library.
The keyboard on device
Wrap a form in KeyboardAvoidingView, or use KeyboardAwareScrollView when it
scrolls — both are in the kit and use the platform Keyboard API.
Set the right keyboard and autofill hints — they cost nothing and improve every form:
<TextInput
label="Email"
keyboardType="email-address"
autoCapitalize="none"
autoComplete="email"
textContentType="emailAddress"
/>Submitting
Keep the button mounted while it works — loading blocks interaction without
unmounting, so focus is never lost mid-request:
<Button loading={saving} onPress={submit}>Save</Button>