FormField
The standard form building block. Composes Text (label), Input, and another Text (hint/error) into a single controlled component. Error state is derived automatically from errorMessage — you never need to set isError on the underlying Input manually.
When to use: Every form field that has a label and/or could show an error. This covers nearly every form input in a Stratum app.
When not to use: Search bars (use SearchBar). Bare inputs with no label or error state (use Input).
Import
Section titled “Import”import { FormField } from '@/components';<FormField label="Email" value={email} onChangeText={setEmail} placeholder="you@example.com" keyboardType="email-address" autoCapitalize="none" isRequired errorMessage={emailError}/>| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Field label |
value | string | — | Controlled value (required) |
onChangeText | (text: string) => void | — | Change handler (required) |
placeholder | string | — | Input placeholder |
isRequired | boolean | false | Appends * to label (visual only) |
hint | string | — | Helper text shown when no error |
errorMessage | string | — | Error text; also triggers error styling on Input |
labelPosition | 'above' | 'inline' | 'above' | Label interaction pattern |
shape | 'default' | 'pill' | 'default' | Input border radius |
isDisabled | boolean | false | Disables interaction |
secureTextEntry | boolean | false | Password masking |
keyboardType | KeyboardTypeOptions | 'default' | |
autoCapitalize | 'none' | 'sentences' | 'words' | 'characters' | 'sentences' | |
autoCorrect | boolean | true | |
returnKeyType | ReturnKeyTypeOptions | — | |
onSubmitEditing | () => void | — | Return key handler |
onFocus | () => void | — | |
onBlur | () => void | — | |
testID | string | — |
Label position
Section titled “Label position”'above' (default) — Coinbase-style
Section titled “'above' (default) — Coinbase-style”Label sits above the input in semibold weight. Best for forms with 3+ fields — the label is always visible so users never lose context while typing.
<FormField label="First name" value={name} onChangeText={setName} labelPosition="above"/>'inline' — Wealthsimple-style
Section titled “'inline' — Wealthsimple-style”Label starts inside the input as a placeholder substitute. On focus or when a value is present, it animates up to a compact floated label at the top of the input. Best for short 1-2 field forms (login, search).
<FormField label="Email" value={email} onChangeText={setEmail} labelPosition="inline" keyboardType="email-address"/>Never mix labelPosition values within the same form.
Error handling
Section titled “Error handling”const [emailError, setEmailError] = useState('');
function validate() { if (!email.includes('@')) { setEmailError('Enter a valid email address'); } else { setEmailError(''); }}
<FormField label="Email" value={email} onChangeText={setEmail} errorMessage={emailError} // drives Input's isError and shows error text onBlur={validate}/>Full login form
Section titled “Full login form”<View style={{ gap: 16 }}> <FormField label="Email" value={email} onChangeText={setEmail} placeholder="you@example.com" keyboardType="email-address" autoCapitalize="none" isRequired errorMessage={errors.email} /> <FormField label="Password" value={password} onChangeText={setPassword} placeholder="8+ characters" secureTextEntry isRequired errorMessage={errors.password} returnKeyType="done" onSubmitEditing={handleSubmit} /> <Button variant="primary" onPress={handleSubmit}>Sign in</Button></View>Do / Don’t
Section titled “Do / Don’t”Do: Use errorMessage — FormField automatically sets the Input’s error state.
Don’t: Set isError on the Input inside FormField — FormField handles this.
Don’t: Mix labelPosition values in the same form.
// ✗ Inconsistent label patterns in the same form<FormField label="Name" labelPosition="above" ... /><FormField label="Email" labelPosition="inline" ... />