skill:react-forms - Type-Safe Forms with React Hook Form + Zod
Version: 1.0.0
Purpose
Build type-safe, validated forms using React Hook Form v7 and Zod v4. A single Zod schema provides client-side validation, server-side validation, and full TypeScript inference via z.infer . This skill covers form setup, field registration, custom validation, error display, multi-step forms, dynamic fields, and Server Action integration. Use when building any form in React/Next.js that needs validation, type safety, or complex field interactions.
File Structure
skills/react-forms/ ├── SKILL.md (this file) └── examples.md
Interface References
-
Context: Loaded via ContextProvider Interface
-
Memory: Accessed via MemoryStore Interface
-
Shared Patterns: Shared Loading Patterns
-
Schemas: Validated against context_metadata.schema.json and memory_entry.schema.json
Form Development Focus Areas
-
Schema Definition: Zod v4 schemas with transforms, refinements, and custom error messages
-
Hook Form Integration: useForm with zodResolver , register patterns, controlled vs. uncontrolled
-
Type Inference: z.infer<typeof schema> for form data types — no manual type definitions
-
Error Handling: Field-level errors, form-level errors, server errors, async validation
-
Complex Forms: Multi-step wizards, dynamic field arrays, conditional fields, dependent validation
-
Server Integration: Zod schema reuse in Server Actions, API routes, and middleware
-
Performance: Uncontrolled components by default, selective re-renders via watch , form state isolation
-
Accessibility: Labels, error announcements, required indicators, focus management on errors
Common Errors Prevented
-
Defining form types manually instead of using z.infer<typeof schema>
-
Using onChange mode when onBlur or onSubmit is sufficient (causes unnecessary re-renders)
-
Forgetting zodResolver in useForm config — validation silently skipped
-
Not handling async default values (use reset() after fetch, not defaultValues )
-
Using register with controlled components (MUI, Radix) — use Controller instead
-
Missing name prop mismatch between schema and register
-
Zod schema not matching form field names exactly (case-sensitive)
-
Not calling handleSubmit on the form's onSubmit — raw form data instead of validated
-
Forgetting to disable submit button during isSubmitting state
-
Using watch() at form level instead of useWatch for individual fields (performance)
MANDATORY WORKFLOW (MUST FOLLOW EXACTLY)
Step 1: Initial Analysis
YOU MUST:
-
Identify the form requirements:
-
Fields, types, and validation rules
-
Submission target (Server Action, API route, client-side)
-
UI library (native HTML, MUI, Radix, shadcn/ui)
-
Determine form complexity:
-
Simple (single page, flat fields)
-
Multi-step wizard
-
Dynamic field arrays
-
Dependent/conditional fields
-
Check existing form patterns in the project
-
Ask clarifying questions if needed
Step 2: Load Memory
Follow Standard Memory Loading with skill="react-forms" and domain="engineering" .
YOU MUST:
-
Use memoryStore.getSkillMemory("react-forms", "{project-name}") to load project patterns
-
Use memoryStore.getByProject("{project-name}") for cross-skill context (design system, component lib)
-
Review existing form conventions, validation patterns, and error display styles
Step 3: Load Context
Follow Standard Context Loading for the engineering domain. Stay within the file budget declared in frontmatter.
Step 4: Implement Forms
YOU MUST follow this pattern:
- Define Zod Schema (single source of truth):
import { z } from "zod"
export const userSchema = z.object({ name: z.string().min(2, "Name must be at least 2 characters"), email: z.string().email("Invalid email address"), age: z.coerce.number().min(18, "Must be 18 or older"), role: z.enum(["admin", "user", "moderator"]), })
// Type is inferred — never define manually export type UserFormData = z.infer<typeof userSchema>
- Wire React Hook Form:
"use client" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { userSchema, type UserFormData } from "./schema"
export function UserForm() { const { register, handleSubmit, formState: { errors, isSubmitting }, } = useForm<UserFormData>({ resolver: zodResolver(userSchema), defaultValues: { name: "", email: "", role: "user" }, })
async function onSubmit(data: UserFormData) { // data is fully typed and validated }
return ( <form onSubmit={handleSubmit(onSubmit)}> {/* Fields with error display */} </form> ) }
- Reuse Schema on Server:
"use server" import { userSchema } from "./schema"
export async function createUser(formData: FormData) { const result = userSchema.safeParse(Object.fromEntries(formData)) if (!result.success) { return { errors: result.error.flatten().fieldErrors } } // result.data is typed as UserFormData await db.users.create({ data: result.data }) }
Rules:
-
Always use zodResolver — never skip client-side validation
-
Always validate on the server too — client validation is a UX convenience, not a security layer
-
Use z.coerce for form fields that come as strings but need other types
-
Prefer onSubmit validation mode for most forms
-
Use Controller for third-party controlled components
-
Use useFieldArray for dynamic field lists
DO NOT define form types manually — always use z.infer
Step 5: Generate Output
-
Save to /claudedocs/react-forms_{project}_{YYYY-MM-DD}.md
-
Follow naming conventions in ../OUTPUT_CONVENTIONS.md
-
Include schema file, form component, and server validation code
Step 6: Update Memory
Follow Standard Memory Update for skill="react-forms" .
Store form patterns, validation conventions, UI component integration patterns, and error display styles.
Compliance Checklist
Before completing, verify:
-
Step 1: Form requirements and complexity identified
-
Step 2: Standard Memory Loading pattern followed
-
Step 3: Standard Context Loading pattern followed
-
Step 4: Schema-first approach with Zod + React Hook Form implemented
-
Step 5: Output saved with standard naming convention
-
Step 6: Standard Memory Update pattern followed
Further Reading
-
React Hook Form: https://react-hook-form.com/
-
Zod: https://zod.dev/
-
@hookform/resolvers: https://github.com/react-hook-form/resolvers
-
Zod v4: https://v4.zod.dev/
Version History
Version Date Changes
1.0.0 2026-02-12 Initial release with interface-based architecture