신청 폼

입력·선택·날짜 컴포넌트를 조합하고 오류 메시지를 처리하는 방법입니다.

프로그램 신청 폼을 예시로 폼 컴포넌트의 조합과 오류 메시지 처리 패턴을 다룹니다.

설치

pnpm dlx shadcn@latest add \
  https://krdscn-ui.smlee.info/r/text-input.json \
  https://krdscn-ui.smlee.info/r/select.json \
  https://krdscn-ui.smlee.info/r/checkbox.json \
  https://krdscn-ui.smlee.info/r/date-input.json \
  https://krdscn-ui.smlee.info/r/button.json

레이블과 도움말

label을 전달하면 <label> 요소와 입력 요소가 자동으로 연결됩니다. hint는 입력 아래 도움말로 렌더링되며 aria-describedby로 연결됩니다.

import { TextInput } from "@/components/ui/krds/(input)/text-input"

export function NameField() {
  return <TextInput label="이름" placeholder="홍길동" hint="주민등록상 이름을 입력하세요" />
}

오류 메시지

검증에 실패하면 error에 메시지를 전달합니다.

export function EmailField() {
  return <TextInput label="이메일" placeholder="you@example.com" error="이메일 형식이 아닙니다" />
}

error 하나로 다음이 함께 처리됩니다.

  • 입력 아래 오류 메시지 렌더링 (KRDS 규칙: 메시지는 항상 컨트롤 아래)
  • 메시지에 role="alert" 적용 — 스크린리더가 즉시 낭독
  • 입력 요소에 aria-invalid="true"aria-describedby 자동 연결
  • 오류 테두리 표시 — 시각 스타일이 aria-invalid 셀렉터에 연결되어 있어 화면과 보조기술 상태가 항상 일치

success, information prop도 같은 방식으로 동작하며 우선순위는 error > success > information > hint입니다.

선택 컴포넌트

Select는 options 배열을 받습니다. Select와 Checkbox 모두 Radix Primitives 기반으로 키보드 인터랙션과 ARIA 속성이 내장되어 있습니다.

import { Checkbox } from "@/components/ui/krds/(selection)/checkbox"
import { Select } from "@/components/ui/krds/(selection)/select"

const PROGRAM_OPTIONS = [
  { value: "tour", label: "견학 프로그램" },
  { value: "edu", label: "교육 프로그램" },
  { value: "event", label: "체험 행사" },
]

export function ProgramFields() {
  return (
    <>
      <Select options={PROGRAM_OPTIONS} label="신청 프로그램" />
      <Checkbox label="개인정보 수집·이용에 동의합니다" />
    </>
  )
}

날짜 입력

DateInput은 달력 팝오버로 값을 선택합니다. 직접 타이핑은 의도적으로 제한되어 있어 자유 입력으로 인한 형식 오류가 발생하지 않습니다.

import { DateInput } from "@/components/ui/krds/(input)/date-input"

export function VisitDateField() {
  return <DateInput label="방문 희망일" />
}

폼 조합

app/apply/page.tsx
"use client"

import { useState } from "react"
import { Button } from "@/components/ui/krds/(action)/button"
import { DateInput } from "@/components/ui/krds/(input)/date-input"
import { TextInput } from "@/components/ui/krds/(input)/text-input"
import { Checkbox } from "@/components/ui/krds/(selection)/checkbox"
import { Select } from "@/components/ui/krds/(selection)/select"

const PROGRAM_OPTIONS = [
  { value: "tour", label: "견학 프로그램" },
  { value: "edu", label: "교육 프로그램" },
]

export default function ApplyPage() {
  const [email, setEmail] = useState("")
  const [submitted, setSubmitted] = useState(false)

  const emailError = submitted && !email.includes("@") ? "이메일 형식이 아닙니다" : undefined

  return (
    <form
      className="mx-auto flex max-w-[480px] flex-col gap-6 py-10"
      onSubmit={(e) => {
        e.preventDefault()
        setSubmitted(true)
      }}
    >
      <TextInput label="이름" placeholder="홍길동" />
      <TextInput
        label="이메일"
        placeholder="you@example.com"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        error={emailError}
      />
      <Select options={PROGRAM_OPTIONS} label="신청 프로그램" />
      <DateInput label="방문 희망일" />
      <Checkbox label="개인정보 수집·이용에 동의합니다" />

      <Button type="submit" size="lg">
        신청하기
      </Button>
    </form>
  )
}

입력 컴포넌트는 특정 폼 라이브러리에 의존하지 않으며 controlled/uncontrolled 모두 지원합니다. react-hook-form, TanStack Form과의 통합은 폼 문서를 참고하세요.

다음 단계

내비게이션에서 메뉴와 경로 표시를 구성합니다.