Form best practices — rules that make forms reliable, accessible, and convenient.
Why it matters: Forms are the primary way users interact with applications — login, registration, checkout, settings. Poorly built forms frustrate users and lose conversions. Good form practice means proper validation, accessible markup, clear error messages, and a smooth submission experience.
1. Use controlled components:
1function Form() {2 const [email, setEmail] = useState("");3 return (4 <input5 type="email"6 value={email}7 onChange={e => setEmail(e.target.value)}8 aria-required="true"9 />10 );11}
2. Validation:
1// Client-side validation (Zod + React Hook Form)2const schema = z.object({3 email: z.string().email(),4 password: z.string().min(8),5});67// Server-side validation (always!)8async function submit(data) {9 const result = schema.safeParse(data);10 if (!result.success) throw new Error("Invalid data");11 await db.users.create(result.data);12}
3. Accessible forms (Accessibility):
1<form onSubmit={handleSubmit}>2 <fieldset>3 <legend>Registration</legend>4 <label htmlFor="email">Email</label>5 <input id="email" type="email" aria-required="true" aria-describedby="email-help" />6 <span id="email-help">Enter your corporate email</span>7 </fieldset>8 <button type="submit">Submit</button>9</form>
4. Loading indicator and errors:
1function SubmitButton({ isPending, error }) {2 return (3 <div>4 <button type="submit" disabled={isPending}>5 {isPending ? "Sending..." : "Submit"}6 </button>7 {error && <div role="alert" className="error">{error.message}</div>}8 </div>9 );10}
5. Enter key handling:
1// All inputs inside <form> submit the form on Enter2// This works automatically!
Common mistakes to avoid:
alert() for error messages — use inline, accessible error text instead.<label>.