Components · 오버레이
Toast (토스트)
화면 가장자리에 잠깐 나타났다 스스로 사라지며 작업 결과를 알린다. Callout/Alert(페이지에 고정 노출되는 상시 안내)와 역할이 다르다 — 토스트는 발행·저장 완료 같은 순간 피드백 전용이다.
정의
화면 가장자리에 잠깐 나타났다 스스로 사라지며 작업 결과를 알리는 순간 피드백 컴포넌트다.
Anatomy
| 부위 | 토큰 |
|---|---|
| ① 컨테이너 | bg --toast-bg(네이비) · fg --toast-fg · 모서리 --toast-radius · 패딩 --toast-pad-y/--toast-pad-x · 그림자 --toast-shadow · z-index: var(--z-toast) |
| ② 상태 아이콘(선택) | 라인 SVG stroke="currentColor" |
| ③ 메시지 | --text-body-sm · 1줄 권장 |
변형
| 변형 | 용도 · 표시 시간 |
|---|---|
| 정보(기본) | 저장·발행 완료 알림 — 2~3초 후 자동 소멸 |
| 경고 | 실패·주의 알림 — 3~4초 유지 + 좌측 상태 바 --color-state-warning-solid |
상태
| 상태 | 규칙 |
|---|---|
| Enter | 아래에서 올라오며 fade-in(--duration-base --easing-out) |
| Visible | 정보 2~3초 / 경고 3~4초 유지(KRDS 기준) |
| Hover(선택) | 커서를 올리면 자동 소멸 타이머 일시정지(권장) |
| Exit | fade-out 후 DOM에서 제거 |
Do / Don't
Do
- 저장·발행 완료 같은 순간 피드백에만 쓰고 스스로 사라지게 한다.
- 메시지는 결과를 한 줄로 담는다("카드뉴스를 저장했습니다").
- 되돌리기 같은 액션이 붙으면 버튼 하나까지만, 아니면 표시 시간을 넉넉히 준다.
Don't
- 폼 필드별 검증 오류에 토스트를 쓰지 말 것 — 인라인 에러(Input Error 상태)를 쓴다.
- 상시 확인해야 할 안내를 토스트로 띄우지 말 것 — 그건 Callout·Alert(페이지 고정)다.
- 토스트를 여러 개 쌓아 화면을 가리지 말 것 — 한 번에 1개, 순차 표시한다.
콘텐츠 규칙
- 메시지는 결과 서술 1문장(≤25자), 완료형으로("발행했습니다").
- 오류 토스트는 무엇이 실패했는지 + 다음 행동을 짧게("발행 실패 — 잠시 후 다시 시도").
- 이모지·느낌표를 남발하지 않는다 — 상태는 아이콘·색으로 전달한다.
키보드 인터랙션
| 키 | 동작 |
|---|---|
Tab | 닫기·액션 버튼이 있을 때만 그 버튼으로 포커스 이동 |
Enter / Space | 포커스된 닫기·액션 버튼 실행 |
Esc | 액션형 토스트 닫기 |
순수 정보 토스트는 포커스를 받지 않는다 — 자동 소멸한다.
접근성
- 컨테이너에 정보는
role="status"+aria-live="polite", 경고·오류는role="alert"+aria-live="assertive"— 스크린리더가 자동 낭독한다. - 소멸 시간이 짧으면 못 읽는 사용자가 생긴다 — 중요한 내용을 토스트에만 의존하지 않는다.
- 색만으로 성패를 구분하지 않는다(아이콘·텍스트 병기). 닫기 버튼은
aria-label="닫기". - 애니메이션은
prefers-reduced-motion에서 제거한다(즉시 표시·제거). 기준:../guidelines/accessibility.md.
코드
html
<button class="pi-button" type="button" data-toast="발행을 완료했습니다.">토스트 띄우기</button>
<div class="pi-toast-region" role="status" aria-live="polite" aria-atomic="true"></div>css
.pi-toast-region {
position: fixed; left: 50%; bottom: var(--space-8); transform: translateX(-50%);
display: flex; flex-direction: column; gap: var(--space-3); align-items: center;
z-index: var(--z-toast); pointer-events: none;
}
.pi-toast {
display: inline-flex; align-items: center; gap: var(--space-3);
padding: var(--toast-pad-y) var(--toast-pad-x);
border-radius: var(--toast-radius);
background: var(--toast-bg);
color: var(--toast-fg);
box-shadow: var(--toast-shadow);
font: var(--text-body-sm);
word-break: keep-all; pointer-events: auto;
opacity: 0; transform: translateY(var(--space-4));
transition: opacity var(--duration-base) var(--easing-out),
transform var(--duration-base) var(--easing-out);
}
.pi-toast.is-visible { opacity: 1; transform: translateY(0); }
.pi-toast--warning { border-left: var(--border-thick) solid var(--color-state-warning-solid); }
@media (prefers-reduced-motion: reduce) { .pi-toast { transition: none; } }js
const region = document.querySelector(".pi-toast-region");
document.querySelectorAll("[data-toast]").forEach((trigger) => {
trigger.addEventListener("click", () => {
const toast = document.createElement("div");
toast.className = "pi-toast";
toast.textContent = trigger.dataset.toast;
region.appendChild(toast);
requestAnimationFrame(() => toast.classList.add("is-visible"));
setTimeout(() => {
toast.classList.remove("is-visible");
toast.addEventListener("transitionend", () => toast.remove(), { once: true });
}, 2500);
});
});Changelog
| 날짜 | 변경 |
|---|---|
| 2026-07-15 | 문서 계약(10섹션) 적용 — Anatomy 도해·정보/경고 변형·표시 시간·자동 소멸 실코드 신설. Callout/Alert와의 역할 구분 명시, KRDS 벤치마킹 규격 승계 |