Form Components
Input controls and form utilities for building data entry interfaces. All form components support labels, error states, and size variants, and integrate seamlessly with react-hook-form via the FormField wrapper.
import {
Input,
TextArea,
Select,
Checkbox,
Switch,
RadioGroup,
SegmentedControl,
SearchBar,
FormField,
} from '@/common/components';Input
A text input field with label, error/helper text, and optional left/right icon slots. Automatically toggles password visibility when secureTextEntry is set. Extends React Native’s TextInputProps.
Basic Usage
<Input label="Email" placeholder="you@example.com" />With Error and Helper Text
<Input label="Username" error="Username is already taken" />
<Input label="Password" helperText="Must be at least 8 characters" secureTextEntry />With Icons
import { Icon } from '@/common/components';
<Input
label="Search"
placeholder="Type to search..."
leftIcon={<Icon name="search-outline" size={18} variant="muted" />}
/>
<Input
label="Email"
rightIcon={<Icon name="checkmark-circle" size={18} variant="accent" />}
value="valid@email.com"
/>Password Input
When secureTextEntry is set, a toggle eye icon is automatically rendered in the right icon slot.
<Input label="Password" secureTextEntry placeholder="Enter password" />Sizes
<Input label="Small" size="sm" />
<Input label="Medium" size="md" />
<Input label="Large" size="lg" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Label text above the input |
error | string | — | Error message below the input (takes precedence over helperText) |
helperText | string | — | Helper text below the input |
disabled | boolean | false | Disable the input |
size | 'sm' | 'md' | 'lg' | 'md' | Size variant |
leftIcon | ReactNode | — | Icon rendered on the left side |
rightIcon | ReactNode | — | Icon rendered on the right side (replaced by password toggle when secureTextEntry is set) |
Input extends TextInputProps, so you can use placeholder, keyboardType, autoCapitalize, onChangeText, value, and all other standard text input props.
TextArea
A multi-line text input with optional character count display. Extends Input props (excluding secureTextEntry).
Basic Usage
<TextArea label="Description" placeholder="Enter a description..." />With Character Count
<TextArea
label="Bio"
maxLength={280}
showCount
placeholder="Tell us about yourself..."
/>Custom Lines
<TextArea label="Notes" numberOfLines={6} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
numberOfLines | number | 4 | Number of visible text lines |
maxLength | number | — | Maximum character count |
showCount | boolean | false | Display character counter below the field |
Plus all Input props except secureTextEntry.
Select
A dropdown picker that opens a bottom sheet with selectable options.
Basic Usage
const [value, setValue] = useState('');
<Select
label="Country"
placeholder="Select a country"
value={value}
onChange={setValue}
options={[
{ label: 'United States', value: 'us' },
{ label: 'United Kingdom', value: 'uk' },
{ label: 'Germany', value: 'de' },
]}
/>With Error and Disabled Options
<Select
label="Plan"
value={plan}
onChange={setPlan}
error="Please select a plan"
options={[
{ label: 'Free', value: 'free' },
{ label: 'Pro', value: 'pro' },
{ label: 'Enterprise (Coming Soon)', value: 'enterprise', disabled: true },
]}
/>SelectOption Type
interface SelectOption {
label: string;
value: string;
disabled?: boolean;
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | required | Currently selected option value |
onChange | (value: string) => void | required | Callback with the new selected value |
options | SelectOption[] | required | Array of selectable options |
placeholder | string | — | Placeholder text when no option is selected |
label | string | — | Label text above the select |
error | string | — | Error message below the select |
disabled | boolean | false | Disable the select |
size | 'sm' | 'md' | 'lg' | 'md' | Size variant |
Checkbox
A toggle checkbox with optional label text and indeterminate state support.
Basic Usage
const [checked, setChecked] = useState(false);
<Checkbox checked={checked} onChange={setChecked} label="I agree to the terms" />Indeterminate State
Used for “select all” checkboxes when some items are selected.
<Checkbox
checked={allSelected}
indeterminate={someSelected && !allSelected}
onChange={handleSelectAll}
label="Select All"
/>Sizes
<Checkbox checked label="Small" size="sm" onChange={() => {}} />
<Checkbox checked label="Medium" size="md" onChange={() => {}} />
<Checkbox checked label="Large" size="lg" onChange={() => {}} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | required | Whether the checkbox is checked |
onChange | (checked: boolean) => void | required | Callback with the new checked value |
label | string | — | Text label beside the checkbox |
disabled | boolean | false | Disable the checkbox |
indeterminate | boolean | false | Show mixed/indeterminate state |
size | 'sm' | 'md' | 'lg' | 'md' | Size variant |
Switch
An on/off toggle with optional label text.
Basic Usage
const [enabled, setEnabled] = useState(false);
<Switch value={enabled} onValueChange={setEnabled} label="Enable notifications" />Without Label
<Switch value={darkMode} onValueChange={toggleDarkMode} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | boolean | required | Current on/off state |
onValueChange | (value: boolean) => void | required | Toggle callback |
label | string | — | Text label beside the switch |
disabled | boolean | false | Disable the switch |
size | 'sm' | 'md' | 'lg' | 'md' | Size variant |
RadioGroup
A single-select option group supporting vertical and horizontal layouts.
Vertical Layout (Default)
const [value, setValue] = useState('email');
<RadioGroup
value={value}
onChange={setValue}
options={[
{ label: 'Email', value: 'email' },
{ label: 'SMS', value: 'sms' },
{ label: 'Push Notification', value: 'push' },
]}
/>Horizontal Layout
<RadioGroup
value={size}
onChange={setSize}
orientation="horizontal"
options={[
{ label: 'S', value: 'sm' },
{ label: 'M', value: 'md' },
{ label: 'L', value: 'lg' },
{ label: 'XL', value: 'xl' },
]}
/>With Disabled Options
<RadioGroup
value={plan}
onChange={setPlan}
options={[
{ label: 'Free', value: 'free' },
{ label: 'Pro', value: 'pro' },
{ label: 'Enterprise (Contact Us)', value: 'enterprise', disabled: true },
]}
/>RadioOption Type
interface RadioOption {
label: string;
value: string;
disabled?: boolean;
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | required | Currently selected option value |
onChange | (value: string) => void | required | Callback with the new selected value |
options | RadioOption[] | required | Array of radio options |
orientation | 'vertical' | 'horizontal' | 'vertical' | Layout direction |
size | 'sm' | 'md' | 'lg' | 'md' | Size variant |
SegmentedControl
An animated tab-style selector. Each segment can optionally include an icon alongside its label.
Basic Usage
const [value, setValue] = useState('list');
<SegmentedControl
value={value}
onChange={setValue}
options={[
{ label: 'List', value: 'list' },
{ label: 'Grid', value: 'grid' },
]}
/>With Icons
import { Icon } from '@/common/components';
<SegmentedControl
value={view}
onChange={setView}
options={[
{ label: 'List', value: 'list', icon: <Icon name="list-outline" size={16} /> },
{ label: 'Grid', value: 'grid', icon: <Icon name="grid-outline" size={16} /> },
{ label: 'Map', value: 'map', icon: <Icon name="map-outline" size={16} /> },
]}
/>SegmentOption Type
interface SegmentOption {
label: string;
value: string;
icon?: ReactNode;
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | required | Currently selected segment value |
onChange | (value: string) => void | required | Callback when a segment is selected |
options | SegmentOption[] | required | Array of segment options |
size | 'sm' | 'md' | 'md' | Size variant |
disabled | boolean | false | Disable all segments |
The active segment indicator animates smoothly between options using React Native’s Animated API.
SearchBar
A search input with a built-in search icon, clear button, and optional loading indicator.
Basic Usage
const [query, setQuery] = useState('');
<SearchBar
value={query}
onChangeText={setQuery}
placeholder="Search products..."
/>With Submit and Clear Handlers
<SearchBar
value={query}
onChangeText={setQuery}
onSubmit={handleSearch}
onClear={handleClear}
placeholder="Search..."
/>Loading State
Replaces the clear button with a spinner while search results are being fetched.
<SearchBar
value={query}
onChangeText={setQuery}
loading={isSearching}
placeholder="Search..."
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | required | Current search text |
onChangeText | (text: string) => void | required | Text change handler |
placeholder | string | — | Placeholder text |
onSubmit | () => void | — | Callback when the user submits (presses return) |
onClear | () => void | — | Callback when the clear button is pressed |
loading | boolean | false | Show a spinner instead of the clear button |
autoFocus | boolean | false | Focus the input on mount |
size | 'sm' | 'md' | 'lg' | 'md' | Size variant |
FormField
A wrapper component that connects any input to react-hook-form’s Controller. It automatically passes value, onChangeText, onBlur, label, and error props to its child input via cloneElement.
Basic Usage
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type FormData = z.infer<typeof schema>;
function LoginForm() {
const { control, handleSubmit } = useForm<FormData>({
resolver: zodResolver(schema),
});
return (
<>
<FormField name="email" control={control} label="Email" required>
<Input placeholder="you@example.com" keyboardType="email-address" />
</FormField>
<FormField name="password" control={control} label="Password" required>
<Input placeholder="Enter password" secureTextEntry />
</FormField>
<Button title="Sign In" onPress={handleSubmit(onSubmit)} />
</>
);
}With Other Input Types
FormField works with any input component that accepts value/onChangeText props.
<FormField name="bio" control={control} label="Bio">
<TextArea placeholder="Tell us about yourself" maxLength={280} showCount />
</FormField>
<FormField name="country" control={control} label="Country" required>
<Select
placeholder="Select a country"
options={countries}
/>
</FormField>Props
| Prop | Type | Default | Description |
|---|---|---|---|
name | FieldPath<T> | required | Field path within the form values object |
control | Control<T> | required | react-hook-form control object |
label | string | — | Label prepended to the child input |
required | boolean | — | When true, appends * to the label |
children | ReactElement | required | Input element that receives form controller props |
FormField uses React.cloneElement to inject props into its child. The child component must accept value, onChangeText, onBlur, label, and error props for the integration to work correctly.