TL;DR
Client-side validation improves UX by giving instant feedback, but it's NOT security. Use it for user experience, but always duplicate validation server-side. These prompts help you build accessible, helpful form validation with libraries like Zod, React Hook Form, or native HTML5.
React Hook Form + Zod
Set up form validation with React Hook Form and Zod.
Install: npm install react-hook-form @hookform/resolvers zod
Create schema: const signupSchema = z.object({ email: z.string().email('Please enter a valid email'), password: z.string() .min(8, 'Password must be at least 8 characters') .regex(/A-Z/, 'Password must contain uppercase letter') .regex(/0-9/, 'Password must contain a number'), confirmPassword: z.string() }).refine(data => data.password === data.confirmPassword, { message: "Passwords don't match", path: 'confirmPassword' });
Use in component: const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(signupSchema) });
Show me the complete component with:
- Real-time validation on blur
- Error messages under each field
- Disabled submit until valid
- Loading state during submission
HTML5 Native Validation
Improve my forms with HTML5 native validation.
Native attributes:
- required: Field must have value
- type="email": Must be valid email format
- type="url": Must be valid URL
- minlength/maxlength: Character limits
- min/max: Number range
- pattern: Custom regex
Example:
Custom validation with JavaScript: input.setCustomValidity('Custom error message'); input.reportValidity();
Style invalid fields: input:invalid { border-color: red; }
Client validation is for UX only: Never rely on it for security. Attackers can disable JavaScript, modify requests, or call your API directly. Server-side validation is mandatory.
Accessible Error Messages
Make my form validation accessible to all users.
Accessibility requirements:
- Error messages linked with aria-describedby
- aria-invalid="true" on invalid fields
- Focus management - focus first error on submit
- Screen reader announcements for errors
- Don't rely on color alone (use icons/text too)
Example:
{errors.email && (
{errors.email.message}
)}
On form submit with errors:
- Prevent submission
- Show error summary at top
- Focus the error summary or first invalid field
- Announce "Form has X errors" to screen readers
Real-Time Validation Patterns
Implement user-friendly validation timing.
Best practices:
- Don't validate on every keystroke (annoying)
- Validate on blur (leaving field)
- Re-validate on change after first error shown
- Validate all on submit attempt
Implementation: const touched, setTouched = useState({}); const errors, setErrors = useState({});
// Validate on blur const handleBlur = (field) => { setTouched({ ...touched, field: true }); validateField(field); };
// Only show error if field touched {touched.email && errors.email && ( {errors.email} )}
Debounce for expensive validations: const debouncedValidate = useMemo( () => debounce(validateUsername, 300), );
// Check username availability after typing stops debouncedValidate(e.target.value)} />
Pro tip: Share validation schemas between frontend and backend. With Zod, you can define once and use in both React Hook Form and your API routes, ensuring consistency.
Should I disable the submit button until form is valid?
It's a UX preference. Disabled buttons can confuse users who don't see errors. An alternative is to allow submission, then show all errors at once with focus on the first error.
How do I validate async (like checking if username exists)?
Use debounced validation on change/blur. Show a loading spinner while checking. Cache results to avoid repeated API calls for the same value.