Input
A text control with a prefix and a suffix. Field adds the label, description and error around it.
Installation
npx axiom add inputpnpm dlx axiom add inputyarn dlx axiom add inputbun x axiom add inputAlso copies field.tsx, the Field parts shared with TextArea and DatePicker, and use-input.ts, which holds the value, focus and accessibility logic.
To keep a focused field above the keyboard in a long form, put the form in a KeyboardAwareScrollView from react-native-keyboard-controller.
Usage
import { Field, Input } from "@/components/ui/input";
<Field>
<Field.Label>Email</Field.Label>
<Input value={email} onChangeText={setEmail} />
<Field.Description>We'll never share it.</Field.Description>
</Field>;Input is the control alone: the box, the text, the prefix and the suffix. The label and the messages are Field parts you place yourself, above, below or next to the control, in any order. Without a Field, give the input an accessibilityLabel:
<Input placeholder="Search" accessibilityLabel="Search" />Props
Input
Input accepts every TextInput prop from React Native (keyboardType, autoComplete, secureTextEntry, returnKeyType…) on top of these:
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Current text. Use with onChangeText for a controlled field. |
defaultValue | string | '' | Initial text when the field manages its own value. |
onChangeText | (text: string) => void | — | Called on every change. |
placeholder | string | — | Hint shown while the field is empty, in content.subtle. Don’t use it as the label. |
prefix | ReactNode | — | Content before the text: an icon, a currency sign, a country code. |
suffix | ReactNode | — | Content after the text: a unit, a clear button, a visibility toggle. |
size | 'sm' | 'md' | 'lg' | 'md' | Fixed height from the input size tokens: 50, 56 or 64pt. |
variant | 'outline' | 'filled' | 'outline' | outline has a border, filled a subtle background with no border. |
invalid | boolean | — | Red border. Inside a Field, it follows the Field. |
disabled | boolean | — | Read-only with the disabled tokens. Maps to editable={false}. Inside a Field, it follows the Field. |
onFocus / onBlur | (event) => void | — | Called when the field gains or loses focus. The focused border is handled for you. |
ref | Ref<TextInput> | — | Gives access to focus(), blur() and clear(), to chain fields on submit. |
containerStyle | StyleProp<ViewStyle> | — | Styles for the box around the text. style goes to the TextInput. |
Field
| Part | What it is |
|---|---|
Field | A View that stacks its children with a gap. Takes invalid, disabled and required. |
Field.Label | The label. With required, it ends with a red *. |
Field.Description | Help under the control, in the muted color. |
Field.Error | The error, in red. Rendering it puts the field in the invalid state: render it only on an error. |
Field passes invalid, disabled and required to the control inside it. When a part’s children are plain text, the control announces them itself: the label as its accessibility label, the error (or else the description) as its hint, and “Required” when it applies. The parts are then hidden from screen readers so nothing is read twice.
Colors come from input.{outline,filled}.{default,focused,invalid,disabled} in the component tokens.
Use cases
Login form
autoComplete and keyboardType give the right keyboard and password autofill; the eye button is a suffix.
<Field required>
<Field.Label>Email</Field.Label>
<Input keyboardType="email-address" autoComplete="email" autoCapitalize="none" />
</Field>
<Field>
<Field.Label>Password</Field.Label>
<Input
secureTextEntry={!visible}
autoComplete="current-password"
suffix={<IconButton icon="eye" size="sm" accessibilityLabel="Show password" onPress={toggle} />}
/>
</Field>Validation on blur
Show the error once the user leaves the field, not while they are still typing.
<Field>
<Field.Label>Postcode</Field.Label>
<Input
value={postcode}
onChangeText={setPostcode}
onBlur={() => setTouched(true)}
/>
{touched && !isPostcode(postcode) ? (
<Field.Error>Postcodes look like NW1 6XE.</Field.Error>
) : null}
</Field>Amount with a prefix and a unit
prefix and suffix hold what the user doesn’t type. The value stays a plain number.
<Field>
<Field.Label>Amount</Field.Label>
<Input size="lg" keyboardType="decimal-pad" prefix="$" suffix="USD" />
<Field.Description>Balance: {balance}</Field.Description>
</Field>Chain fields with the return key
returnKeyType="next" and a ref move focus to the next field, so the user never has to reach for it.
const lastName = useRef<TextInput>(null);
<Field>
<Field.Label>First name</Field.Label>
<Input returnKeyType="next" onSubmitEditing={() => lastName.current?.focus()} />
</Field>
<Field>
<Field.Label>Last name</Field.Label>
<Input ref={lastName} returnKeyType="done" />
</Field>