Text field
Free · MITReact Native · ExpoA text field whose label glides up as you focus it, with helper and error text that slide in, a shake and haptic on error, clear and reveal buttons, a character counter and a multiline mode that grows with its text.
Installation
$ npx shadcn@latest add https://goodui.dev/native/r/text-field.jsonThen run npx expo install --fix so react-native-reanimated, react-native-svg match your Expo SDK. Files land in components/goodui-native/text-field.
Usage
import { useState } from "react";
import { TextField } from "@/components/goodui-native/text-field/text-field";
export function EmailField() {
const [email, setEmail] = useState("");
const [error, setError] = useState<string>();
return (
<TextField
label="Email"
placeholder="name@example.com"
value={email}
onChangeText={(t) => {
setEmail(t);
setError(undefined);
}}
onBlur={() => setError(email.includes("@") ? undefined : "Enter a valid email address")}
error={error}
helperText="We'll send the receipt here"
keyboardType="email-address"
autoComplete="email"
autoCapitalize="none"
/>
);
}When to use
- Form fields: name, email, address, card holder, notes.
- Passwords and secrets (secureTextEntry adds the reveal toggle).
- Free text with a limit, such as a bio or a review (showCount with maxLength).
- Notes and messages that should grow while typing (multiline with minRows and maxRows).
- Search: use search-bar, which has Cancel, scopes and debouncing.
- Verification codes: use otp-input.
- Numbers changed by small steps (guests, quantity): use quantity-stepper.
- Picking from a fixed list: use segmented-control or a radio group from checkbox.
Examples
Password with reveal toggle
<TextField label="Password" secureTextEntry textContentType="password" autoComplete="password" />Growing note with a limit
<TextField label="Note for the driver" multiline minRows={2} maxRows={6} maxLength={200} showCount />Amount with a unit and an icon
<TextField label="Amount" keyboardType="decimal-pad" leading={<CardIcon />} trailing={<Text tone="secondary">EUR</Text>} />Focus the next field
const next = useRef<ComponentRef<typeof TextInput>>(null);
<TextField label="First name" returnKeyType="next" onSubmitEditing={() => next.current?.focus()} />
<TextField label="Last name" inputRef={next} />API reference
| Prop | Type | Default |
|---|---|---|
| label Floating label. It rests inside the field like a placeholder and glides up to the top edge when the field is focused or has text. Sentence case. | string | — |
| value Text value (controlled). Pair it with `onChangeText`. Omit it to let the field keep its own text. | string | — |
| defaultValue Initial text when uncontrolled. | string | "" |
| onChangeText Called on every change, including the clear button (with ""). | (text: string) => void | — |
| placeholder Example input such as "name@example.com". With a label it shows only once the label has floated and the field is still empty; without a label it is the placeholder. | string | — |
| helperText Supporting text under the field. An error message replaces it while present. | string | — |
| error Error state. A string is shown under the field in place of the helper text. When an error appears (or changes) the field shakes once, plays the error haptic and is announced. `true` marks the field red without a message. | string | boolean | — |
| leading Icon before the text, about 20 pt. Color it with `textSecondary`. | ReactNode | — |
| trailing Icon or short text after the input, for example a unit ("kg") or a badge. | ReactNode | — |
| clearable Shows a clear button while the field is focused and has text. Default `true` for single-line fields that are not secure. | boolean | — |
| secureTextEntry Hides the text and adds an eye button that reveals it. | boolean | false |
| maxLength Maximum number of characters. | number | — |
| showCount Shows a "12/80" counter under the field. Needs `maxLength`. Reaching the limit plays a warning haptic. | boolean | false |
| multiline Multiline field that grows with its text between `minRows` and `maxRows`, then scrolls. | boolean | false |
| minRows Rows a multiline field starts with. Default 3. | number | 3 |
| maxRows Rows a multiline field grows to before it scrolls. Default 8. | number | 8 |
| variant `filled` is a quiet muted surface; `outline` draws a hairline border for dense forms on a plain surface. | "filled" | "outline" | "filled" |
| size Heights 48 (`sm`) and 56 pt (`md`). | "sm" | "md" | "md" |
| disabled Dims the field and makes it read-only. | boolean | false |
| haptic `false` silences this field's haptics (error, limit reached, reveal toggle, clear). | boolean | true |
| onFocus Called when the input gains focus. | () => void | — |
| onBlur Called when the input loses focus. | () => void | — |
| inputRef Ref to the underlying TextInput, for example to focus the next field from `onSubmitEditing`. | Ref<ComponentRef<typeof TextInput>> | — |
| accessibilityLabel Screen reader label. Defaults to `label`, then `placeholder`. | string | — |
| accessibilityHint Screen reader hint. The error or helper text is used when omitted. | string | — |
| labels Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. | Partial<TextFieldLabels> | — |
| style Style of the outer wrapper (field plus message line). | StyleProp<ViewStyle> | — |
| testID Identifier for end-to-end tests. | string | — |
Motion and haptics
- Label: rests at body size inside the field and glides up and shrinks to caption size on the morph spring when focused or filled; its color follows focus (accent) and error (danger).
- Focus draws a 2 pt accent ring; an error turns the ring and label red and tints the filled surface.
- An error that appears or changes shakes the field once.
- Helper and error text slide down into place; the message line's height springs, so content below glides instead of jumping.
- The clear button scales in and its slot opens on the snappy spring; the reveal toggle's slash draws across or retracts.
- Multiline: the field's height springs to fit its text between minRows and maxRows, then scrolls.
- Reduced motion: the label moves with a short fade-speed timing instead of a spring, no shake, no scaling, heights change instantly; colors still change.
- Haptic: error when an error appears or changes (never on mount)
- Haptic: warning once when the text reaches maxLength
- Haptic: select on the reveal toggle
- Haptic: tap on clear
- Label, ring, colors, shake and heights animate on the UI thread through Reanimated shared values.
- Typing re-renders the field once per keystroke like any controlled TextInput; nothing animates per keystroke except the multiline height when the line count changes.
Accessibility
- The TextInput is the accessible element. Its label is accessibilityLabel, then label, then placeholder; the floating label itself is hidden from screen readers.
- The hint is accessibilityHint, else the error or helper text, so the reason for an error is read with the field.
- A new error message is announced with AccessibilityInfo.announceForAccessibility.
- Clear is a button labelled 'Clear text' and is hidden from screen readers while invisible. The reveal toggle is a button labelled 'Show password' or 'Hide password'.
- Icon buttons have a 12 pt (clear) or 10 pt (reveal) hitSlop so they reach a 44 pt target.
- On the web a secure field autofills as current-password; Android's 'password' and 'password-new' map to current-password and new-password there.
Notes for AI
- Use label for the field name and placeholder for an example value. Do not pass both as the same text.
- Pass error as a string to show a message, or true for a red state without one; set it back to undefined to clear it. The field shakes and buzzes by itself, so do not add your own shake or haptic.
- It works controlled (value + onChangeText) or uncontrolled (defaultValue).
- Use inputRef, not ref, to reach the TextInput (for focus() from returnKeyType='next').
- Inside a ScrollView set keyboardShouldPersistTaps='handled' so the clear and reveal buttons work while the keyboard is up.
- clearable defaults to true for single-line, non-secure fields; secureTextEntry fields get the reveal toggle instead.
Related
An iOS-style search field: focusing it shrinks the field while a Cancel button springs in beside it and scope chips cascade in below. Clear button, loading spinner and debounced queries built in.
A verification code field where digits pop in, a pending check breathes, success ripples across the cells and a wrong code shakes and clears.
A − value + stepper whose value rolls in the direction of change. Hold a button to repeat with acceleration; the ends push back with a rubber-band bounce and a warning haptic. In a cart the − turns into a trash button at the minimum.
A press-responsive button with haptics that runs async actions on its own: it gathers into a spinner, then opens into a success or error state.