Skip to content

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 { FormField } from '@/components';
<FormField
label="Email"
value={email}
onChangeText={setEmail}
placeholder="you@example.com"
keyboardType="email-address"
autoCapitalize="none"
isRequired
errorMessage={emailError}
/>
PropTypeDefaultDescription
labelstringField label
valuestringControlled value (required)
onChangeText(text: string) => voidChange handler (required)
placeholderstringInput placeholder
isRequiredbooleanfalseAppends * to label (visual only)
hintstringHelper text shown when no error
errorMessagestringError text; also triggers error styling on Input
labelPosition'above' | 'inline''above'Label interaction pattern
shape'default' | 'pill''default'Input border radius
isDisabledbooleanfalseDisables interaction
secureTextEntrybooleanfalsePassword masking
keyboardTypeKeyboardTypeOptions'default'
autoCapitalize'none' | 'sentences' | 'words' | 'characters''sentences'
autoCorrectbooleantrue
returnKeyTypeReturnKeyTypeOptions
onSubmitEditing() => voidReturn key handler
onFocus() => void
onBlur() => void
testIDstring

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"
/>

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.

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}
/>
<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: 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" ... />
  • Input — bare input atom
  • AuthForm — complete auth form organism using FormField
  • SearchBar — search-specific input molecule