Claude-skill-registry create-form
Set up hybrid client/server validated forms with Zod and React Hook Form. Use when creating forms with validation.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/create-form" ~/.claude/skills/majiayu000-claude-skill-registry-create-form && rm -rf "$T"
manifest:
skills/data/create-form/SKILL.mdsource content
Create Form
When to Use
- Creating any form with validation
- User asks to "add a form" or "create a form"
Critical Rule
// CORRECT - Use <form> with manual fetcher.submit() <form onSubmit={handleSubmit(onSubmit)}> // WRONG - Causes submission conflicts <fetcher.Form onSubmit={handleSubmit(onSubmit)}>
Core Pattern
- Same Zod schema on client and server
- Client: Instant feedback with React Hook Form
- Server: Security with
validateFormData() - Auto error sync: Server errors populate form fields
Quick Start
1. Schema (app/lib/validations.ts
)
app/lib/validations.tsimport { z } from 'zod'; export const contactFormSchema = z.object({ name: z.string().min(1, 'Name is required'), email: z.string().email('Invalid email'), }); export type ContactFormData = z.infer<typeof contactFormSchema>;
2. Server Action
import { validateFormData } from '~/lib/form-validation.server'; import { zodResolver } from '@hookform/resolvers/zod'; export async function action({ request }: Route.ActionArgs) { const formData = await request.formData(); const { data, errors } = await validateFormData<ContactFormData>( formData, zodResolver(contactFormSchema) ); if (errors) return data({ errors }, { status: 400 }); await processData(data!); return redirect('/success'); }
3. Client Form
import { useFetcher } from 'react-router'; import { useValidatedForm } from '~/lib/form-hooks'; export default function ContactPage() { const fetcher = useFetcher(); const { register, handleSubmit, formState: { errors } } = useValidatedForm({ resolver: zodResolver(contactFormSchema), errors: fetcher.data?.errors, }); const onSubmit = (data: ContactFormData) => { const formData = new FormData(); formData.append('name', data.name); fetcher.submit(formData, { method: 'POST' }); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <TextInput {...register('name')} error={errors.name?.message} /> <Button type="submit">Submit</Button> </form> ); }
Checklist
- Define Zod schema in
app/lib/validations.ts - Add server action with
validateFormData() - Use
hook in componentuseValidatedForm - Use
(not<form>
) with<fetcher.Form>handleSubmit
Templates
Full Reference
See
.github/instructions/form-validation.instructions.md for:
- Field-level vs form-level errors
- Conditional validation
- File uploads
- Complex schema patterns