# Scaffold Form

> Scaffold a React Hook Form wired to the feature's zod request contract via zodResolver, submitting through a TanStack Query mutation hook, with accessible fields and surfaced errors.

- Skill: `thibautbaissac/scaffold-form` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add thibautbaissac/scaffold-form`
- Raw SKILL.md: https://api.skillmd.com/api/skills/thibautbaissac/scaffold-form/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: thibautbaissac (https://skillmd.com/u/thibautbaissac)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/thibautbaissac/scaffold-form

---


# Scaffold a form

Build `$form` with React Hook Form validated against the feature's existing zod
request contract. The form owns input state; a mutation hook owns submission.

## Step 1 — Locate the contract and feature

Find the owning feature and its request schema (`CreateXBody`, `UpdateXBody`). If
no contract exists yet, stop and run `/add-api-contract` first — the form
validates against that schema, it does not define one.

## Step 2 — Ensure dependencies

Check for `react-hook-form` and `@hookform/resolvers`. Install only what is
missing through the detected package manager. Reuse the project's existing field
primitives in `shared/ui` (`Field`, `Input`, `FormError`) before adding new ones.

## Step 3 — Build the form

Apply `references/form-patterns.md`.
- Type `useForm` as `z.infer` of the request schema; resolve with
  `zodResolver(<RequestBody>)`.
- Derive `defaultValues` from typed data (edit) or schema-shaped literals (create).
- Register inputs against schema field names; render `formState.errors` tied to
  inputs with `aria-invalid` / `aria-describedby`.

## Step 4 — Wire submission through a mutation

Call the feature's TanStack Query mutation hook (`useCreateX` / `useUpdateX`); do
not `fetch` in the component. Disable submit with the mutation's `isPending`. Map
server failures back onto fields with `setError`, or to a form-level message.

## Step 5 — Verify

```bash
source "${CLAUDE_PROJECT_DIR}/.claude/lib/detect-toolchain.sh"
run_typecheck
run_tests
```

Confirm the resolver schema is the same one the API parses, and no parallel form
schema or `interface` was introduced.

## Checklist

- [ ] Resolver uses the feature's existing zod request schema; values are its `z.infer`.
- [ ] No second form schema or parallel `interface`.
- [ ] Submission goes through a `useMutation` hook; no `fetch`/invalidation in the form.
- [ ] Submit disabled via `isPending`; field and server errors surfaced accessibly.
- [ ] Generic field primitives live in `shared/ui`; the domain form stays in its feature.
- [ ] `run_typecheck` and `run_tests` pass.

