codingstairs
노트에듀라이프연락
⌕검색⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

연락하기

로그인 없이도 보낼 수 있어요. 답변이 필요하면 이메일을 함께 적어 주세요.

  • 익명 폼으로 의견 남기기 →
  • ✉ warragon112@gmail.com
  • 카카오톡 오픈채팅 ↗

© 2026 codingstairs

  • 노트
  • 에듀
  • 검색
  • 라이프
  • 연락
  • 약관
  • RSS
  • GitHub
에듀›HTML/CSS/JS 부터 React, Next, Tailwind 까지›8단계

8단계

8단계 — 폼·검증·UX 마무리

0회 조회

8단계 — 폼·검증·UX 마무리

화면을 예쁘게 그리는 데서 한 발 더 — 사용자가 실수해도 친절하게 알려주는 게 진짜 프론트엔드입니다.

zod 로 입력값 검증

zod 는 입력 모양을 스키마 로 정의하는 라이브러리예요.

import { z } from "zod";

const SignupSchema = z.object({
  email: z.string().email("이메일 형식이 아니에요"),
  password: z.string().min(8, "비밀번호는 8자 이상"),
  age: z.number().int().min(14, "14세 이상만 가입 가능"),
});

const result = SignupSchema.safeParse(formData);
if (!result.success) {
  // result.error.issues 에 어떤 필드가 왜 틀렸는지 들어 있음
}

이 한 패턴으로 모든 폼 의 검증이 통일됩니다.

로딩 UX — 사용자 시야에 머무는 0.4 초

서버 응답을 기다리는 사이 화면이 멈춰 있으면 사용자는 고장났다 고 느껴요. 세 가지가 큰 차이를 만듭니다.

  1. 버튼 비활성화 + 텍스트 변경 — <button disabled>{loading ? "보내는 중…" : "보내기"}</button>
  2. 스켈레톤 UI — 콘텐츠 자리에 회색 박스 (animate-pulse)
  3. 낙관적 업데이트 — 응답 전이라도 화면을 미리 갱신, 실패 시 되돌림
const [loading, setLoading] = useState(false);

async function onSubmit() {
  setLoading(true);
  try {
    await fetch("/api/signup", { method: "POST", body: ... });
  } finally {
    setLoading(false);
  }
}

접근성 (a11y) 한 줄

<input> 옆엔 항상 <label> 을, <button> 텍스트는 행동(submit / cancel) 이 드러나게.

<label for="email">이메일</label>
<input id="email" type="email" required>

스크린리더 사용자도 어떤 항목을 채우는지 알아야 해요.

직접 해 보기

5~7단계의 Counter 를 증가/감소/리셋 세 버튼으로 확장해 보세요. 각 버튼에 aria-label 을 붙이고, 0 일 때 감소 버튼은 disabled 로.

더 깊이

  • 폼 + Zod 노트
  • 로딩 UX 노트
  • 시맨틱 HTML 노트

다음 단계

여기까지 폼으로 텍스트 를 받았어요. 마지막 9단계 — 이미지 업로드와 최적화 에서는 사용자가 올리는 이미지 를 받습니다 — 검증·sharp 변환·사이즈 프리셋까지.

← 7단계

7단계 — Tailwind CSS

9단계 →

9단계 — 이미지 업로드와 최적화