.github/skills/tsh-implementing-forms/SKILL.md
Form architecture, schema-based validation, field composition, error handling, multi-step form flows, and accessible form patterns. Use when building forms, implementing validation, creating multi-step wizards, or integrating form fields with a component library.
npx skillsauth add thesoftwarehouse/copilot-collections tsh-implementing-formsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Provides patterns and workflows for building robust, accessible forms with schema-based validation, composable field components, and multi-step form flows.
<principles> <schema-first-validation> Define validation rules as a separate schema, co-located with but decoupled from UI code. The schema is the single source of truth for what constitutes valid data. Infer TypeScript types from schemas to eliminate drift between validation rules and form types. Never duplicate type definitions manually — derive them from the schema. </schema-first-validation> <progressive-disclosure-of-errors> Show validation errors at the right moment: on field blur or form submission, not on every keystroke. Help users fix errors rather than frustrate them with premature feedback. After the first validation pass, switch to on-change validation so users see errors clear as they correct them. </progressive-disclosure-of-errors> <accessible-by-default> Every form field must have a visible label. Error messages must be announced to screen readers via `role="alert"` or `aria-live` regions. The form must be fully navigable by keyboard alone — Tab through fields, Enter to submit, Escape to cancel where applicable. Associate error messages with their fields using `aria-describedby`. </accessible-by-default> </principles>Use the checklist below and track progress:
Progress:
- [ ] Step 1: Define the data model
- [ ] Step 2: Build field components
- [ ] Step 3: Compose the form
- [ ] Step 4: Handle multi-step flows (if applicable)
- [ ] Step 5: Verify accessibility
Step 1: Define the data model
*.schema.ts or validation/ directory). The schema declares every constraint: required fields, min/max lengths, patterns, custom rules, cross-field dependencies.Step 2: Build field components
Create composable field wrapper components that connect the form library's state to the component library's inputs. Each field wrapper:
<label>, the input element, and an error message container.aria-invalid="true" when the field has an error.aria-describedby pointing to the error message element's id.aria-required="true" for required fields.<input type="file"> visually and use a styled trigger button with aria-label. Display selected file name(s), size, and a remove action. Validate file type and size in the schema — reject invalid files before upload begins.Step 3: Compose the form
Assemble field components into a form:
novalidate attribute to the <form> element when using custom validation — this disables browser-native validation bubbles that conflict with the form library's error display. The schema-based validation from Step 1 replaces the browser's built-in constraints.<form> behavior — do not break it with preventDefault on the wrong element).Step 4: Handle multi-step flows (if applicable)
When a form spans multiple steps or pages:
Step 5: Verify accessibility
Use the tsh-ensuring-accessibility skill for a thorough audit. At minimum, verify:
<input>, <select>, and <textarea> has an associated <label> element (via for/id pairing or wrapping).role="alert" or are in an aria-live="polite" region so screen readers announce them.aria-required).| Validation mode | When to use | Behavior | | ------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | On submit | Simple forms, few fields | Validate all fields on submit, show all errors at once | | On blur | Complex forms, many required fields | Validate each field when the user leaves it | | On change (with debounce) | Real-time feedback needed (password strength, username availability) | Validate as user types, debounced to avoid excessive checks | | Mixed | Best UX for most forms | Validate on blur initially; after the first error, switch to on-change so errors clear immediately when corrected |
| Pattern | Description | | ------------------ | ---------------------------------------------------------------------------------------------------- | | Inline below field | Error message directly under the invalid field — most common, recommended default | | Summary at top | List of all errors at the top of the form — useful for long forms, aids screen readers | | Inline + summary | Both inline and summary — best accessibility (screen reader reads summary, sighted users see inline) | | Toast/notification | Only for server-side submission errors, never for field-level validation |
Form:
- [ ] Validation schema defined separately from UI
- [ ] TypeScript type inferred from schema (no manual duplication)
- [ ] Every field has a visible label
- [ ] Error messages shown inline below fields
- [ ] Errors announced to screen readers (role="alert" or aria-live)
- [ ] Submit button disabled during submission
- [ ] Server-side errors mapped to specific fields
- [ ] Tab order follows visual layout
- [ ] Form submittable via Enter key
- [ ] Loading state during async submission
| Anti-Pattern | Instead Do |
| ----------------------------------------- | ----------------------------------------------- |
| Manual validation in event handlers | Use a schema-based validation library |
| Duplicating types between schema and form | Infer types from the schema |
| Showing errors on every keystroke | Use blur or mixed-mode validation |
| Unlabeled inputs (placeholder as label) | Always use visible <label> elements |
| Generic "Form has errors" message | Specific per-field error messages |
| Losing form data on back-navigation | Persist state across steps |
| Ignoring server-side errors | Map API errors to specific form fields |
| Submit button without loading state | Disable button + show spinner during submission |
tsh-implementing-frontend — for component composition patterns and framework-specific references (form library integration, validation library choice)tsh-ensuring-accessibility — for WCAG compliance in form fields, labels, and error announcementstsh-writing-hooks — for custom form-related hooks/composables (useFormField, useMultiStepForm)tsh-reviewing-frontend — for form-specific review criteria during code reviewdevelopment
Custom hook and composable patterns — naming, composition, stable return shapes, lifecycle cleanup, and testing strategies. Use when writing reusable logic units (React hooks, Vue composables), refactoring logic into hooks, debugging hook behavior, or reviewing hook implementations.
testing
UI verification criteria, structure checklists, severity definitions, and tolerance rules for comparing implementations against Figma designs. Use for verifying UI matches design, understanding what to check, and determining acceptable differences.
development
Clean raw workshop or meeting transcripts from small talk, filler words, and off-topic tangents. Extract and structure business-relevant content into a standardized format with discussion topics, key decisions, action items, and open questions.
development
Discover and establish technical context before implementing any feature. Prioritize project instructions, existing codebase patterns, and external documentation in that order. Use for any task requiring understanding of project conventions, coding standards, architecture patterns, and established practices before writing code.