FormField
FormField associates one form control with its label, helper text, error message, and field state.
Open in Storybook
Import
import { FormField } from "@tosui/react";
Basic usage
- Preview
- Code
<FormField label="Email">
<Input type="email" placeholder="you@example.com" />
</FormField>
Helper text
- Preview
- Code
<FormField label="Password" helperText="Must be at least 8 characters">
<Input type="password" placeholder="Enter password" />
</FormField>
Required field
- Preview
- Code
<FormField label="Username" isRequired>
<Input placeholder="Enter username" />
</FormField>
Invalid state
When isInvalid is true, errorMessage replaces helperText.
- Preview
- Code
<FormField
label="Email"
errorMessage="Please enter a valid email address"
isInvalid
isRequired
>
<Input type="email" placeholder="you@example.com" />
</FormField>
Disabled state
- Preview
- Code
<FormField label="Disabled Field" disabled>
<Input placeholder="Cannot edit" />
</FormField>
Tosui controls
Tosui controls consume FormField state from context. Each FormField represents one semantic form control.
- Preview
- Code
<VStack gap={6}>
<FormField label="Country" isRequired>
<Select>
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
</Select>
</FormField>
<FormField label="Bio" helperText="Tell us about yourself">
<Textarea rows={4} placeholder="Your bio..." />
</FormField>
<FormField
label="Terms"
errorMessage="You must accept the terms"
isInvalid
>
<Checkbox label="I accept the terms and conditions" />
</FormField>
</VStack>
Native and custom controls
For a native or custom control, pass a function as children. Spread the received props onto the element that receives focus.
<FormField
label="Email"
helperText="We'll only use this for account notifications"
isRequired
>
{(controlProps) => (
<input
type="email"
placeholder="you@example.com"
{...controlProps}
/>
)}
</FormField>
The function receives id, disabled, required, aria-describedby, and aria-invalid. The function prevents FormField from guessing how a custom component forwards props.
Complete form example
<VStack gap={4}>
<FormField label="Full Name" isRequired>
<Input placeholder="John Doe" />
</FormField>
<FormField
label="Email"
helperText="We'll never share your email"
isRequired
>
<Input type="email" placeholder="you@example.com" />
</FormField>
<FormField label="Role">
<Select>
<option value="">Select a role</option>
<option value="developer">Developer</option>
<option value="designer">Designer</option>
<option value="manager">Manager</option>
</Select>
</FormField>
<FormField label="Message" helperText="Optional">
<Textarea placeholder="Your message..." />
</FormField>
<Button type="submit" w="100%">Submit</Button>
</VStack>
Props reference
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | - | Required label text |
| helperText | string | - | Helper text below the control |
| errorMessage | string | - | Error message (shown when isInvalid) |
| isRequired | boolean | false | Show required asterisk |
| isInvalid | boolean | false | Show invalid state |
| disabled | boolean | false | Disable the control |
| id | string | auto-generated | Custom ID for the field |
| children | ReactElement | (controlProps => ReactElement) | - | One Tosui control or one control adapter |
Accessibility
FormField provides these relationships:
- A
<label>points to the control ID throughhtmlFor. aria-describedbypoints to the active helper text or error message.- Existing
aria-describedbyIDs remain on Tosui controls. aria-invalidistruewhenisInvalidistrue.requiredanddisabledreach the native control.- A generated ID links the label and the control when
idis absent.
State propagation
Tosui controls consume these values through context:
idisInvaliddisabledrequiredaria-describedbyaria-invalid
Context crosses component and layout boundaries. A layout wrapper does not receive control-only props.
TypeScript
import {
FormField,
type FormFieldControlProps,
type FormFieldProps,
} from "@tosui/react";