Step 8
Step 8 — Forms, validation, UX
0 views
Step 8 — Forms, validation, UX
Beyond pretty markup — real frontend is kindly handling user mistakes.
Validate input with zod
import { z } from "zod";
const SignupSchema = z.object({
email: z.string().email("Not a valid email"),
password: z.string().min(8, "At least 8 characters"),
age: z.number().int().min(14, "14 or older"),
});
const result = SignupSchema.safeParse(formData);
if (!result.success) {
// result.error.issues lists the failed fields
}
One pattern, all your forms.
Loading UX — those 400ms matter
If the screen freezes during a request, the user thinks it's broken. Three habits:
- Disable button + change label —
<button disabled>{loading ? "Sending…" : "Send"}</button> - Skeleton UI — gray placeholder with
animate-pulse - Optimistic update — show the result first, roll back on failure
const [loading, setLoading] = useState(false);
async function onSubmit() {
setLoading(true);
try { await fetch("/api/signup", { method: "POST", body: ... }); }
finally { setLoading(false); }
}
Accessibility one-liner
Every <input> needs a <label>. Every <button> text should describe the action.
<label for="email">Email</label>
<input id="email" type="email" required>
Screen-reader users need to know which field they're filling.
Try it
Extend the Counter with three buttons (increment / decrement / reset). Add aria-label to each, and disable decrement when n is 0.
Next
So far your forms received text. The final Step 9 — Image Upload and Optimization receives the image the user uploads — validation, sharp transforms, and size presets.