Skip to content
Agentmart
Submit a tool

form-handling

Form design — state, validation, error feedback.

Skill by Dev-Toolbelt

IndexedMIT★ 7
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/dev-toolbelt-form-handling

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Form design — state, validation, error feedback. Framework-agnostic.

Use cases: design

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/form-handling

What's inside

Skill name
form-handling
Description
Form design — state, validation, error feedback. Framework-agnostic.
License
MIT
Instructions
100 lines in SKILL.md · view on GitHub

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Form Handling

## Detect the Project's Form Library

Before writing any form code, check for an existing library:

| Signal | Library |
|---|---|
| `react-hook-form`, `useForm`, `register`, `Controller` | React Hook Form |
| `formik`, `useFormik`, `<Formik>` | Formik |
| `vee-validate`, `useField`, `useForm` (Vue) | VeeValidate |
| `@angular/forms`, `FormBuilder`, `FormGroup`, `ReactiveFormsModule` | Angular Reactive Forms |
| `zod`, `yup`, `valibot` imports alongside form code | Schema validation library |

If no library is detected: `useState` per field is acceptable for forms with 1–3 fields. For anything larger, recommend adopting the project's preferred library before building.

---

## Core Rules

### Controlled inputs by default
Always bind input value to state and update state on change. Uncontrolled inputs (`ref`-only) are acceptable only for file inputs or third-party widgets that require direct DOM access.

### Validation placement

| Trigger | When to use |
|---|---|
| `onBlur` | Default for most fields — validates after the user leaves the field |
| `onChange` | Use only after the first submission attempt, or for fields with instant feedback (password strength) |
| `onSubmit` | Always run a full validation pass on submit, regardless of field-level validation |

Never validate `onChange` from the first keystroke on empty fields — it creates errors before the user has had a chance to type.

### Schema-first validation
Define validation rules in a single schema (Zod, Yup, Valibot, or equivalent) rather than scattered `if` statements in handlers. The schema is the single source of truth for what constitutes valid data.

```ts
// good — rules live in one place, reusable on the server too
const orderSchema = z.object({
  quantity: z.number().min(1).max(999),
  email: z.string().email(),
})
```

### Error messages
- One error message per field, displayed below the field it belongs to
- Link the message to the input via `aria-describedby` (the field's `id` matches the error element's `id`)
- Use plain language: "Enter a valid email address", not "Invalid format"
- Clear the error as soon as the field becomes valid again

### Submit state
- Disable the submit button while the form is submitting (see `double-submission prevention` in the agent)
- Show a loading indicator during async submission
- On success: clear the form or navigate away — never leave a successfully submitted form in a dirty state
- On error: surface the server error near the form (not just in a toast), keep field values intact

### Reset and dirty tracking
- Track whether the form is dirty (has unsaved changes) to warn the user before navigation
- `reset()` must restore every field to its initial value, not just clear them to empty

---

## Library-Specific Notes

### React Hook Form
- Prefer `register` over `Controller` for native inputs — less overhead
- Use `Controller` for third-party components (date pickers, select libraries)
- Set `mode: 'onBlur'` as the default validation trigger
- Use `resolver` (zod, yup) to keep validation out of the component

### Formik
- Use `validationSchema` with Yup rather than the `validate` function for complex forms
- Avoid `setFieldValue` inside `onChange` handlers — use Formik's `handleChange` to avoid double renders
- `enableReinitialize` should be `false` by default; set to `true` only when the form must reflect external data changes

### VeeValidate (Vue)
- Use `defineRule` + `configure` globally for shared rules; use inline `rules` only for one-off cases
- Pair with `zod` via `@vee-validate/zod` for schema-first validation
- `useForm` + `useField` (Composition API) is preferred over the Options API component approach

### Angular Reactive Forms
- Always use `FormBuilder` — never instantiate `FormGroup` / `FormControl` manually
- Validators go in the `FormControl` definition, not in the template
- Use `updateOn: 'blur'` on controls that should validate after focus loss
- `AbstractControl.statusChanges` is the right hook for cross-field dependent validation — avoid manual `valueChanges` hacks

---

## Anti-Patterns

| Anti-Pattern | Problem |
|---|---|
| Validation logic duplicated in component and service | Single source of truth broken; rules drift apart |
| Showing all errors before the user interacts | Hostile UX — validate progressively |
| Resetting only visible fields on submit | Hidden state leaks into the next submission |
| Calling `event.preventDefault()` manually and then forgetting to handle errors | Leaves the form in a broken state on failure |
| Storing form state in a global store | Forms are transient UI state — keep them local |
| `any` type for form values | Defeats the purpose of schema validation |

From Dev-Toolbelt/dev-team-agents, MIT.

More from Dev-Toolbelt/dev-team-agents

Signals

Stars
7
Forks
0
Open issues
0
Last commit (this folder)
Oct 18, 2026 (10 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from Dev-Toolbelt

Cite this page

form-handling skill. Agent Mart. https://agentmart.dev/skills/dev-toolbelt-form-handling (last updated 2026-10-24)