③ 폼 · 검증 · 저장
입력받고, 틀린 것을 알려주고, 저장합니다. 오류는 색이 아니라 문장으로 알리는 것이 핵심입니다.
배치 순서
| # | 구획 | 쓰는 컴포넌트 |
|---|---|---|
| 1 | 단계 표시기 (3단계 이상일 때만) | 단계 표시기 |
| 2 | 섹션별 카드 — 라벨·입력·도움말·오류 | 카드 · 입력 |
| 3 | 하단 액션 — 취소(ghost) / 저장(primary) | 버튼 |
| 4 | 저장 후 알림 | 토스트 |
규칙
오류는 문장으로
<label class="jt-label" for="code" data-required>품목코드</label>
<input class="jt-input" id="code" aria-invalid="true" aria-describedby="e1">
<div class="jt-error" id="e1">코드는 CP-JT + 숫자 4자리입니다. 예: CP-JT0299</div>세 가지가 한 세트입니다 — aria-invalid · aria-describedby · .jt-error 문장.
빨간 테두리만으로는 색각 이상 사용자가 무엇이 틀렸는지 알 수 없습니다.
좋은 오류 문장은 예시를 포함합니다. “형식이 올바르지 않습니다”보다 “CP-JT + 숫자 4자리입니다. 예: CP-JT0299”가 낫습니다.
사과하지 마세요
| 이렇게 ✅ | 이러지 마세요 🚫 |
|---|---|
| 코드는 CP-JT + 숫자 4자리입니다 | 죄송합니다. 입력값에 문제가 있습니다 |
| 제조일자가 유통기한보다 늦습니다 | 오류가 발생했습니다 |
| 이미 등록된 LOT입니다 | 잘못된 요청입니다 |
저장 중에는 버튼을 잠급니다
<button class="jt-btn" data-loading="true">저장 중</button>data-loading="true" 하나로 스피너와 pointer-events:none이 함께 걸립니다.
두 번 눌려 중복 저장되는 사고가 실제로 가장 흔합니다.
필수 표시는 라벨에
<label class="jt-label" data-required>품목명</label>필드 옆에 별표를 흩뿌리지 말고 라벨에 붙입니다. 그리고 필수가 아닌 것이 더 많다면 반대로 “(선택)“을 붙이는 편이 읽기 쉽습니다.
고정 height 금지
번역하면 라벨과 도움말이 길어집니다. min-height만 쓰세요.
여러 줄 입력은 resize:vertical로 사용자가 늘릴 수 있게 둡니다.
저장 결과 알림
| 상황 | 무엇을 |
|---|---|
| 저장 성공 (계속 작업) | 토스트 — 3초 뒤 사라짐 |
| 저장 성공 (흐름 종료) | 결과 화면 |
| 저장 실패 | 토스트 금지. 알림이나 오류 문장 — 사라지면 안 됩니다 |
하지 말 것
- placeholder를 라벨 대신 쓰기 — 입력을 시작하면 사라져 무슨 칸인지 잊습니다
- 오류를 폼 맨 위에만 모아두기 — 어느 칸이 문제인지 찾아야 합니다. 칸 옆에도 붙이세요
- 저장 버튼을 화면 밖으로 밀어내기 — 긴 폼은 하단 액션바를
position:sticky로 - 실패를 토스트로 알리기
접근성
- 모든 입력에
<label for>연결. 아이콘만 있는 컨트롤은aria-label - 오류 요약을
role="alert"로 두면 저장 실패 시 즉시 읽힙니다 - 도움말·placeholder는
--color-text-secondary(4.6:1).--color-text-tertiary는 글자에 쓰지 않습니다 - 날짜는 OS 기본 달력을 씁니다 — 직접 만든 달력은 키보드·다국어에서 거의 항상 집니다