Form & FormField
Form components with built-in state management, validation, and field binding. Automatically handles value updates, error display, and accessibility.
Form ArchitectureContext-based
Form provides context for state management and validation. FormField wraps input controls and automatically connects them to the form context.
Form handles:
- Values state
- Errors state
- Touched state
- Validation
- Submit handling
FormField handles:
- Value binding
- Error display
- Label rendering
- Helper text
- Accessibility
Basic Form
Submitted Values
Submit the form to see values here
<Form
initialValues={{ name: '', email: '', message: '' }}
validate={(values) => {
const errors: FormErrors = {};
if (!values.name) errors.name = 'Name is required';
if (!values.email) errors.email = 'Email is required';
else if (!/\S+@\S+\.\S+/.test(values.email)) {
errors.email = 'Invalid email format';
}
if (!values.message) errors.message = 'Message is required';
return errors;
}}
onSubmit={(values) => console.log(values)}
>
{({ isSubmitting }) => (
<>
<FormField name="name" label="Name" required>
<Input placeholder="Enter your name" />
</FormField>
<FormField name="email" label="Email" required helperText="We'll never share your email">
<Input type="email" placeholder="you@example.com" />
</FormField>
<FormField name="message" label="Message" required>
<Textarea placeholder="Your message..." autoResize minRows={3} />
</FormField>
<Button type="submit" variant="primary" isLoading={isSubmitting}>
Send Message
</Button>
</>
)}
</Form>Validation Modes
Control when validation runs with validateOnChange and validateOnBlur props.
// Validate on every change (default)
<Form validateOnChange={true} validateOnBlur={true} ...>
// Validate only on blur
<Form validateOnChange={false} validateOnBlur={true} ...>
// Validate only on submit
<Form validateOnChange={false} validateOnBlur={false} ...>Async Validation
The validate function can return a Promise for async validation (e.g., checking username availability).
<Form
initialValues={{ username: '' }}
validate={async (values) => {
const errors: FormErrors = {};
if (!values.username) {
errors.username = 'Username is required';
} else {
// Simulate API check
const isAvailable = await checkUsernameAvailability(values.username);
if (!isAvailable) {
errors.username = 'Username is already taken';
}
}
return errors;
}}
onSubmit={handleSubmit}
>
<FormField name="username" label="Username" required>
<Input placeholder="Choose a username" />
</FormField>
<Button type="submit">Register</Button>
</Form>Label Positions
FormField supports different label positions: top (default), left, and right.
// Label above input (default)
<FormField name="email" label="Email" labelPosition="top">
<Input />
</FormField>
// Label to the left
<FormField name="email" label="Email" labelPosition="left">
<Input />
</FormField>
// Label to the right (useful for checkboxes)
<FormField name="agree" label="I agree to terms" labelPosition="right">
<Checkbox />
</FormField>
// Hidden label (still accessible via screen readers)
<FormField name="search" label="Search" hideLabel>
<Input placeholder="Search..." leftIcon={<Icon name="search" size="sm" />} />
</FormField>Form Render Props
Form accepts children as a render function, providing access to form state and methods.
<Form initialValues={...} onSubmit={...}>
{({
values, // Current form values
errors, // Current validation errors
touched, // Which fields have been touched
isSubmitting, // Whether form is being submitted
isValid, // Whether form has no errors
setFieldValue, // Manually set a field value
setFieldError, // Manually set a field error
setFieldTouched,// Manually mark a field as touched
getFieldProps, // Get binding props for a field
}) => (
<div>
{/* Conditionally show submit button */}
<Button
type="submit"
disabled={!isValid || isSubmitting}
isLoading={isSubmitting}
>
Submit
</Button>
{/* Show current values for debugging */}
<pre>{JSON.stringify(values, null, 2)}</pre>
</div>
)}
</Form>