codingstairs
NotesEDULifeContact
⌕Search⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

Get in touch

Send without signing in. Add your email if you'd like a reply.

  • Leave a message anonymously →
  • ✉ warragon112@gmail.com
  • KakaoTalk Open Chat ↗

© 2026 codingstairs

  • Notes
  • EDU
  • Search
  • Life
  • Contact
  • Legal
  • RSS
  • GitHub
EDU›From HTML/CSS/JS to React, Next.js, Tailwind›Step 8

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:

  1. Disable button + change label — <button disabled>{loading ? "Sending…" : "Send"}</button>
  2. Skeleton UI — gray placeholder with animate-pulse
  3. 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.

← Step 7

Step 7 — Tailwind CSS

Step 9 →

Step 9 — Image Upload and Optimization