Form Component
Form Component
Section titled “Form Component”A robust, accessible form component built on Radix UI primitives with comprehensive validation, error handling, and submission management. Designed for complex forms with real-time validation and seamless user experience.
Features
Section titled “Features”- ♿ Accessibility First: Built on Radix UI Form primitives with ARIA compliance
- 🔍 Real-time Validation: Client-side validation with immediate feedback
- 🚀 Advanced Submission: Custom handlers for validation, success, and error states
- 🎯 Type Safety: Full TypeScript support with form data typing
- 🔧 Flexible: Works with any form fields and validation libraries
- ⚡ Performance: Optimized for large forms with efficient re-renders
- 🌐 Progressive Enhancement: Works without JavaScript as fallback
Live Examples
Section titled “Live Examples”Basic Contact Form
Section titled “Basic Contact Form”A simple contact form demonstrating core functionality:
<Form onSubmit={(e) => { e.preventDefault(); console.log('Form submitted!');}}> <div> <label htmlFor="email">Email Address</label> <input type="email" id="email" name="email" required placeholder="Enter your email" /> </div>
<div> <label htmlFor="message">Message</label> <textarea id="message" name="message" required rows="4" placeholder="Tell us about your project..." /> </div>
<button type="submit">Send Message</button></Form>Advanced Form with Validation
Section titled “Advanced Form with Validation”Example showing custom validation, error handling, and success states:
const handleRegistration = async (formData) => { try { const response = await fetch('/api/register', { method: 'POST', body: formData });
if (!response.ok) { throw new Error('Registration failed'); }
return await response.json(); } catch (error) { throw error; }};
<Form onValidate={(formData) => { const email = formData.get('email'); const password = formData.get('password');
// Client-side validation if (!email?.includes('@')) return false; if (!password || password.length < 8) return false;
return true; }} onFormSuccess={(formData) => { console.log('Registration successful:', formData); router.push('/welcome'); }} onFormError={(error) => { console.error('Registration error:', error); setErrorMessage(error.message); }}> {/* Form fields */}</Form>Form with Loading States
Section titled “Form with Loading States”Demonstrating loading states and disabled form management:
const [isSubmitting, setIsSubmitting] = useState(false);
<Form onSubmit={async (event) => { event.preventDefault(); setIsSubmitting(true);
try { const formData = new FormData(event.currentTarget); await submitForm(formData); } finally { setIsSubmitting(false); } }}> {/* Form fields */}
<button type="submit" disabled={isSubmitting} > {isSubmitting ? 'Saving...' : 'Save Changes'} </button></Form>API Reference
Section titled “API Reference”Form Props
Section titled “Form Props”Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
onSubmit | ((event: React.FormEvent<HTMLFormElement>) => void) | undefined | Optional | — | Form submission handler called after validation passes |
clearOnSubmit | boolean | undefined | Optional | — | Whether to clear form fields after successful submission |
preventDefaultSubmission | boolean | undefined | Optional | — | Whether to prevent default browser form submission |
onValidate | ((formData: FormData) => boolean | Promise<boolean>) | undefined | Optional | — | Custom validation handler called before submission |
onFormError | ((error: Error) => void) | undefined | Optional | — | Error handler for form submission errors |
onFormSuccess | ((formData: FormData) => void) | undefined | Optional | — | Success handler called after successful form submission |
| Prop | Type | Default | Description |
|---|---|---|---|
onSubmit | (event: FormEvent) => void | - | Standard form submission handler |
onValidate | (formData: FormData) => boolean | Promise<boolean> | - | Custom validation function |
onFormSuccess | (formData: FormData) => void | - | Called on successful form submission |
onFormError | (error: Error) => void | - | Called when form submission fails |
clearOnSubmit | boolean | false | Clear form after successful submission |
preventDefaultSubmission | boolean | false | Prevent default form submission behavior |
children | React.ReactNode | - | Form content and fields |
className | string | - | Additional CSS classes |
Form Data Handling
Section titled “Form Data Handling”The Form component provides several ways to handle form data:
FormData API
Section titled “FormData API”<Form onValidate={(formData) => { const email = formData.get('email') as string; const age = Number(formData.get('age'));
return email.includes('@') && age >= 18;}}> {/* Form fields */}</Form>Object Conversion
Section titled “Object Conversion”const formDataToObject = (formData) => { return Object.fromEntries(formData.entries());};
<Form onFormSuccess={(formData) => { const data = formDataToObject(formData); console.log(data); // { email: 'user@example.com', name: 'John' }}}> {/* Form fields */}</Form>Validation Patterns
Section titled “Validation Patterns”Client-Side Validation
Section titled “Client-Side Validation”const validateForm = (formData) => { const email = formData.get('email'); const password = formData.get('password'); const confirmPassword = formData.get('confirmPassword');
// Email validation if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { return false; }
// Password validation if (!password || password.length < 8) { return false; }
// Password confirmation if (password !== confirmPassword) { return false; }
return true;};
<Form onValidate={validateForm}> {/* Form fields */}</Form>Async Validation
Section titled “Async Validation”const validateAsync = async (formData) => { const username = formData.get('username');
// Check username availability try { const response = await fetch(`/api/check-username/${username}`); const { available } = await response.json(); return available; } catch (error) { console.error('Validation error:', error); return false; }};
<Form onValidate={validateAsync}> {/* Form fields */}</Form>Integration with Validation Libraries
Section titled “Integration with Validation Libraries”import { z } from 'zod';
const schema = z.object({ email: z.string().email(), password: z.string().min(8), age: z.number().min(18)});
const validateWithZod = (formData) => { try { const data = { email: formData.get('email'), password: formData.get('password'), age: Number(formData.get('age')) };
schema.parse(data); return true; } catch (error) { console.error('Validation errors:', error.errors); return false; }};
<Form onValidate={validateWithZod}> {/* Form fields */}</Form>Error Handling
Section titled “Error Handling”Error Types
Section titled “Error Types”The Form component handles various error scenarios:
<Form onFormError={(error) => { if (error.name === 'ValidationError') { setValidationErrors(error.details); } else if (error.name === 'NetworkError') { setNetworkError('Please check your connection'); } else { setGeneralError('Something went wrong'); } }}> {/* Form fields */}</Form>Error Recovery
Section titled “Error Recovery”const [submitAttempts, setSubmitAttempts] = useState(0);const maxAttempts = 3;
<Form onFormError={(error) => { setSubmitAttempts(prev => prev + 1);
if (submitAttempts < maxAttempts) { // Show retry option setShowRetry(true); } else { // Disable form after max attempts setFormDisabled(true); } }}> {/* Form fields */}</Form>Accessibility
Section titled “Accessibility”The Form component prioritizes accessibility and follows ARIA best practices:
Screen Reader Support
Section titled “Screen Reader Support”- Proper form landmarks and structure
- Field labels associated with inputs
- Error messages announced to screen readers
- Success confirmations communicated
Keyboard Navigation
Section titled “Keyboard Navigation”- Tab order respects visual layout
- Enter key submits forms appropriately
- Escape key can cancel submissions (custom)
- Arrow keys for radio/checkbox groups
Focus Management
Section titled “Focus Management”const formRef = useRef(null);
<Form ref={formRef} onFormError={(error) => { // Focus first invalid field const firstInvalid = formRef.current?.querySelector(':invalid'); firstInvalid?.focus(); }}> {/* Form fields */}</Form>Accessibility Best Practices
Section titled “Accessibility Best Practices”// ✅ Good: Clear labels and structure<Form> <fieldset> <legend>Contact Information</legend>
<div> <label htmlFor="name">Full Name *</label> <input id="name" name="name" required aria-describedby="name-help" /> <div id="name-help">Enter your full legal name</div> </div> </fieldset></Form>
// ❌ Avoid: Missing labels and unclear structure<Form> <input placeholder="Name" /> <input placeholder="Email" /></Form>Performance Optimization
Section titled “Performance Optimization”Large Forms
Section titled “Large Forms”For forms with many fields, consider performance optimizations:
import { memo } from 'react';
const FormField = memo(({ label, name, type }) => ( <div> <label htmlFor={name}>{label}</label> <input id={name} name={name} type={type} /> </div>));
<Form> {fields.map(field => ( <FormField key={field.name} {...field} /> ))}</Form>Debounced Validation
Section titled “Debounced Validation”import { useDebouncedCallback } from 'use-debounce';
const debouncedValidation = useDebouncedCallback( (formData) => validateForm(formData), 300);
<Form onValidate={debouncedValidation}> {/* Form fields */}</Form>Integration Examples
Section titled “Integration Examples”With React Hook Form
Section titled “With React Hook Form”import { useForm } from 'react-hook-form';
const MyForm = () => { const { register, handleSubmit, formState: { errors } } = useForm();
return ( <Form onSubmit={handleSubmit(onSubmit)}> <input {...register('email', { required: true })} /> {errors.email && <span>Email is required</span>} </Form> );};With Formik
Section titled “With Formik”import { Formik } from 'formik';
<Formik initialValues={{ email: '', password: '' }} onSubmit={handleSubmit}> {({ handleSubmit: formikSubmit }) => ( <Form onSubmit={formikSubmit}> {/* Form fields */} </Form> )}</Formik>Related Components
Section titled “Related Components”- FormField - Individual form field wrapper
- FormInput - Enhanced input component
- FormLabel - Accessible form labels
- FormMessage - Validation messages
- FormSubmit - Form submission button
- Button - For form actions
Source Code
Section titled “Source Code”View the source code for this component:
- Form.tsx - Main component implementation
- Form Stories - Storybook examples
Changelog
Section titled “Changelog”- v1.0.0: Initial form component with Radix UI integration
- v1.1.0: Added validation handlers and error management
- v1.2.0: Enhanced accessibility and TypeScript support
- v1.3.0: Added success/error callbacks and performance optimizations
For detailed TypeScript definitions and additional API information, see the API Documentation.