Why Forms Are So Hard to Get Right
Forms look simple. A few inputs, a button, done. But they're actually the hardest UI to build well. Validation, error states, loading states, accessibility, keyboard navigation, multi-step flows, responsive layout. Get any of these wrong and people leave. Here's how to get them right.
Structure First
form-structure.tsx
Validation That Helps
Bad validation yells at people. Good validation guides them:
- Validate on blur, not on type. Don't show errors while someone is still typing.
- Clear errors when they fix it. Remove the error as soon as the input becomes valid.
- Say what's wrong AND how to fix it. Not "Invalid input." Instead: "Email must include @."
- Mark required fields. Either mark required or optional. Not both. Most forms mark optional since most fields are required.
- Use native validation when possible.
type="email",required,patternwork great.
validation.tsx
Loading and Success States
submit-states.tsx
Accessibility Checklist
- Every input has a visible
<Label>linked with htmlFor - Error messages are linked to inputs with
aria-describedby - Invalid inputs have
aria-invalid="true" - Required fields are marked with
aria-required="true" - Tab order follows visual order
- Form can be submitted with Enter key
- Focus moves to first error after failed submission
UX Tips
- Single column. Multi-column forms are harder to scan. One column is almost always better.
- Big enough inputs. Minimum height of 40px. 44px is better for touch.
- Autofocus the first field. Save users a click.
- Use the right input type.
type="email"shows the @ keyboard on mobile.type="tel"shows the number pad. - Break long forms into steps. Show progress. Let people go back.
- Save progress. If the form is long, save to localStorage so people don't lose their work.
The Short Version
- Label every input. Use htmlFor. No exceptions.
- Validate on blur, not on type. Clear errors when fixed.
- Show what's wrong and how to fix it
- Use React Hook Form + Zod for validation
- Always show loading and success states on submit
- Single column, big inputs, right input types
- Break long forms into steps with saved progress