Core Concepts
Understanding the mental models and architecture of Hero Hook Form.
Architecture Overview
Hero Hook Form is built on three core pillars:
- React Hook Form - Form state management and validation
- HeroUI Components - Beautiful, accessible UI components
- TypeScript - Type safety and developer experience
Key Principle: Hero Hook Form wraps React Hook Form and HeroUI to provide a type-safe, accessible form building experience with minimal boilerplate.
Form Lifecycle
Forms go through four main stages:
1. Initialization
The form is created with schema and field configuration:
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { name: "", email: "" }
});2. Rendering
Fields are rendered based on configuration:
<FormField config={fieldConfig} form={form} />3. Validation
Validation happens on blur, change, or submit:
const isValid = await form.trigger();4. Submission
Form data is validated and submitted:
const handleSubmit = (data) => {
// Process validated data
};Validation can be triggered automatically (onBlur, onChange) or manually using form.trigger().
Data Model
Form Configuration
interface FormConfig<T> {
fields: FormFieldConfig<T>[];
layout?: "vertical" | "horizontal" | "grid";
defaultValues?: Partial<T>;
}Field Configuration
interface FormFieldConfig<T> {
name: Path<T>;
type: "input" | "textarea" | "select" | "checkbox" | "switch" | "radio";
label?: string;
description?: string;
// ... type-specific props
}Form State
interface FormState {
isSubmitting: boolean;
isSubmitted: boolean;
isSuccess: boolean;
errors: FieldErrors<T>;
values: T;
}Field Types
Text Fields
- Input - Single-line text input
- Textarea - Multi-line text input
- Select - Dropdown selection
Boolean Fields
- Checkbox - Checkbox input
- Switch - Toggle switch
Choice Fields
- Radio - Radio button group
- Select - Dropdown selection
Specialized Fields
- Date - Date picker
- File - File upload
- Slider - Numeric slider
- FontPicker - Font selection
Dynamic Fields
- Conditional - Show/hide based on form data
- FieldArray - Dynamic repeating fields
- DynamicSection - Grouped conditional fields
Content Fields
- Content - Headers, questions, or custom content between fields
Validation Patterns
Schema-First Validation
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});Cross-Field Validation
const schema = z.object({
password: z.string(),
confirmPassword: z.string(),
}).refine(data => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"],
});Conditional Validation
const schema = z.object({
hasPhone: z.boolean(),
phone: z.string().optional(),
}).refine(data => !data.hasPhone || data.phone, {
message: "Phone is required",
path: ["phone"],
});Performance Considerations
Memoization
All field components are wrapped with React.memo to prevent unnecessary re-renders.
Field components automatically memoize, so you don’t need to wrap them yourself.
Debounced Validation
Use useDebouncedValidation for expensive validation operations:
import { useDebouncedValidation } from "@rachelallyson/hero-hook-form";
const { debouncedTrigger, isDebouncing } = useDebouncedValidation(
form,
{ delay: 300 }
);Without debouncing, validation runs on every keystroke, which can be expensive for complex validation logic.
Error Handling
Client-Side Errors
- Field-level validation errors
- Form-level validation errors
- Custom validation rules
Server-Side Errors
import { applyServerErrors } from "@rachelallyson/hero-hook-form";
// Apply server errors to form
applyServerErrors(form, {
field1: "Server error message",
field2: "Another error",
});Error Display
- Inline field errors
- Toast notifications
- Modal error dialogs
- Custom error components
Accessibility
ARIA Attributes
All form fields include proper ARIA attributes:
aria-labelfor field labelsaria-describedbyfor field descriptionsaria-invalidfor validation statearia-requiredfor required fields
Keyboard Navigation
- Tab order follows logical form flow
- Enter key submits forms
- Escape key cancels forms
- Arrow keys navigate radio groups
Screen Reader Support
- Field labels are properly associated
- Error messages are announced
- Form state changes are communicated
- Loading states are indicated
Testing
Form Test Utilities
import { createFormTestUtils } from "@rachelallyson/hero-hook-form";
const testUtils = createFormTestUtils(form);
await testUtils.submitForm();
const fieldValue = testUtils.getField("fieldName");Cypress Integration
import { simulateFormSubmission } from "@rachelallyson/hero-hook-form/cypress";
cy.get('[data-testid="form"]').then(simulateFormSubmission);Best Practices
Form Design
- Use clear, descriptive labels - Help users understand what each field is for
- Group related fields together - Use sections or visual grouping
- Provide helpful descriptions - Add context with field descriptions
- Show validation errors immediately - Use
mode: "onBlur"ormode: "onChange" - Use appropriate input types -
email,tel,password, etc.
Performance
- Field components are already memoized - No need to wrap them
- Debounce expensive validations - Use
useDebouncedValidationhook - Use conditional fields - Only render what’s needed
- Avoid unnecessary re-renders - Let React Hook Form handle state
For large forms, consider using conditional fields to only render visible sections.
Accessibility
- Always provide labels - Required for screen readers
- Use semantic HTML - HeroUI components handle this automatically
- Ensure keyboard navigation - Tab order follows form flow
- Test with screen readers - Verify ARIA attributes work correctly
- Provide error feedback - Errors are announced to screen readers
Hero Hook Form components include proper ARIA attributes by default, but you should always provide labels.
Type Safety
- Define proper TypeScript interfaces - Or use Zod schema inference
- Use Zod schemas for validation - Single source of truth for types and validation
- Leverage type inference - Let TypeScript infer types from schemas
- Avoid
anytypes - Use proper generics - Use proper generic constraints - Ensure type safety throughout
Field names must match your form data type. Use Path<T> types for nested fields.