Toast
먼저 보고, 그다음 계약을 읽습니다
화면 모서리에 쌓이는 비동기 알림 — sonner 래퍼 + AXE 토큰/클래스.
Component intent의도와 경계 읽기
Toaster provider 를 루트에 한 번 마운트하고, 어디서든 imperative `toast.*` 로 호출한다. sonner 를 감싸 --bg-elevated 표면·좌측 시맨틱 accent bar·light/dark 자동(theme="system")을 입혔고, 내부 마크업에 .axe-toast/.axe-toast__* 클래스를 classNames prop 으로 주입한다. AI-first 4-state 에 맞춰 loading→success/danger 갱신(같은 id 재사용)과 toast.promise 자동 전이를 지원하며, sonner.error 의 별칭으로 우리 시맨틱인 toast.danger 를 추가로 노출한다.
예제
SSOT에 등록된 실제 API 기준 snippet입니다. standalone 배지만 독립 실행 단위이며, fragment는 주변 state·handler 문맥을 생략합니다.
// app/layout.tsx — 루트에 한 번import { Toaster } from "@axe/ui";<Toaster position="bottom-right" />// 어디서든import { toast } from "@axe/ui";toast.success("저장됨");toast.danger("저장 실패 — 네트워크 오류");import { toast } from "@axe/ui";// 수동 전이const id = toast.loading("Agent 실행 중…");try { const r = await runAgent(); toast.success(`완료 — ${r.summary}`, { id });} catch (e) { toast.danger(humanizeApiError(e), { id });}// 자동 전이toast.promise(runAgent(), { loading: "Agent 실행 중…", success: (r) => `완료 — ${r.summary}`, error: (e) => humanizeApiError(e),});Props
TSX 소스가 진실입니다. 주요 export 컴포넌트의 public API만 노출합니다.
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
position | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" | — | "bottom-right" | 토스트 스택이 뜨는 화면 모서리. |
theme | "light" | "dark" | "system" | — | "system" | 표면 톤. 기본 system — 표면 색이 CSS 변수 기반이라 실제 시각은 토큰이 결정하지만, sonner 내부 상태 클래스에 이 값이 전달된다. |
expand | boolean | — | false | false 면 겹쳐 쌓고 hover 시에만 펼침, true 면 항상 펼쳐 나열. |
closeButton | boolean | — | true | 각 토스트에 닫기(✕) 버튼(.axe-toast__close)을 표시할지. |
visibleToasts | number | — | 4 | 동시에 보이는 최대 개수. 초과분은 뒤에 큐잉. |
duration | number | — | 4000 | 자동 dismiss 까지의 ms. loading 토스트는 갱신/dismiss 전까지 유지된다. |
className | string | — | — | viewport 루트에 .axe-toast-viewport 와 함께 병합. (개별 토스트가 아니라 sonner 컨테이너에 붙는다.) |
...rest | Omit<ToasterProps, "className"> | — | — | 그 외 sonner Toaster 옵션(offset·gap·richColors·hotkey·containerAriaLabel·toastOptions 등)을 그대로 전파. |
.axe-* 클래스 계약
React 밖에서도 같은 표면을 그리는 공개 계약입니다. stable은 minor 버전 안에서 이름을 바꾸지 않습니다.
| 클래스 | 안정성 | 용도 |
|---|---|---|
.axe-toast | stable | 개별 토스트 루트 — elevated 배경·좌측 3px accent bar·shadow-md. |
.axe-toast--success | stable | success 토스트 — 좌측 bar = --success. |
.axe-toast--info | stable | info 토스트 — 좌측 bar = --info. |
.axe-toast--warning | stable | warning 토스트 — 좌측 bar = --warning. |
.axe-toast--danger | stable | danger/error 토스트 — 좌측 bar = --danger (sonner error 에 매핑). |
.axe-toast--loading | stable | loading 토스트 — 좌측 bar = --accent. |
.axe-toast__title | stable | 제목 줄 — semibold·text-primary. |
.axe-toast__description | stable | 보조 설명 — text-secondary·text-xs. |
.axe-toast__action | stable | 액션 버튼 — cta 배경. |
.axe-toast__cancel | stable | 취소 버튼 — 투명 배경·subtle 테두리. |
.axe-toast__close | stable | 닫기(✕) 버튼 — 원형·subtle 배경. |
.axe-toast-viewport | internal | viewport 컨테이너 마커(className 기본). 실제 톤 오버라이드는 [data-sonner-toaster] 속성 셀렉터가 담당 — 전용 규칙 없음. |
접근성
키보드, ARIA, 구현 노트를 함께 검토합니다.
- Alt+T(sonner 기본 hotkey)로 토스트 알림 영역에 포커스
- 각 토스트는 tabIndex 0 — Tab 으로 순회, 액션/닫기 버튼으로 진입
sonner 가 알림 영역을 <section aria-live="polite" aria-relevant="additions text" aria-atomic="false" aria-label="Notifications Alt+T"> 로 렌더 — 새 토스트가 polite 로 발화된다(danger/error 도 기본 polite, assertive 아님). 닫기 버튼 aria-label="Close toast".
모든 애니메이션은 prefers-reduced-motion 을 sonner 가 존중. provider 는 앱당 한 번만 마운트(여러 개면 발화·스택이 갈린다).