Toast
화면 한쪽에 잠깐 떠올랐다 사라지는 알림입니다. 사용자의 흐름을 끊지 않고 작업 결과를 전달할 때 씁니다.
import { ToastProvider, useToast } from 'neba';
// 앱 전체를 한 번 감쌉니다
<ToastProvider position="bottom-end">{children}</ToastProvider>;
// 그 아래 어디서든
const toast = useToast();
toast.add({ color: 'success', title: '배포 완료', description: 'production · 4분 02초' });Toast는 컴포넌트가 아니라 hook으로 띄웁니다. 겉모습은 ToastProvider에서 한 번 정하고, 호출부에서는 내용만 넘깁니다.
Props
ToastProvider
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| locale | string | — | BCP 47 태그. 모든 toast의 × 이름을 이 언어로 씁니다 |
| variant공통 | 'solid' | 'outline' | 'text' | 'outline' | 표면의 무게. 토스트 하나가 따로 덮어쓸 수 있습니다 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 타입 스케일과 여백 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 기본 색 계열 |
| density공통 | 'default' | 'compact' | 'default' | 여백만 바꿉니다 |
| position | `top-${Align}` | `bottom-${Align}` | 'bottom-end' | 스택이 놓이는 자리. 위·아래 두 값과 공용 Align의 조합입니다. 화면 한복판을 세로로 가르는 스택은 만들 수 없습니다 |
| timeout | number | 5000 | 기본 유지 시간(ms). 0이면 닫을 때까지 남습니다 |
| limit | number | 3 | 동시에 보이는 개수. 넘친 것은 버려지지 않고 스택이 빠지면 나타납니다 |
| width | number | string | 380 | 토스트 하나의 최대 너비 |
| closeLabel | string | — | × 버튼의 접근성 이름 |
| classNames | NebaSlots<'viewport' | 'toast' | 'title' | 'description' | 'action' | 'close'> | — | 루트 뒤에 있는 각 파트의 class. 루트 자체는 className이 맡으므로 root 키는 없습니다 |
useToast().add(options)
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| title | ReactNode | — | 제목 |
| description | ReactNode | — | 아래 설명. 이것만 있으면 한 줄짜리 토스트입니다 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | — | 이 토스트만 다른 색 계열로 |
| variant공통 | 'solid' | 'outline' | 'text' | — | 이 토스트만 다른 표면으로 |
| icon | ReactNode | false | — | 앞머리 글리프. 기본값은 color에 딸린 그림 |
| timeout | number | — | 이 토스트의 유지 시간(ms). 0은 읽고 나서 조치가 필요한 메시지에 씁니다 |
| priority | 'low' | 'high' | 'low' | high는 스크린 리더의 말을 끊습니다. 오류는 그럴 만하고 저장 완료는 아닙니다 |
| actionLabel | ReactNode | — | 액션 버튼의 라벨. 넘기면 버튼이 생깁니다 |
| onAction | (event) => void | — | 액션 버튼을 눌렀을 때 |
| id | string | — | 같은 id로 다시 부르면 그 토스트를 제자리에서 갱신하고 타이머를 다시 시작합니다 |
| onClose | () => void | — | 어떤 방식으로든 닫혔을 때 |
| onRemove | () => void | — | 애니메이션이 끝나고 DOM에서 빠졌을 때 |
hook은 add 외에 close(id?), update(id, options), promise(promise, { loading, success, error }), toasts를 함께 돌려줍니다.
예시
position
스택이 붙을 자리입니다. 세로 방향(top/bottom)과 NebaAlign을 조합한 한 단어로 지정합니다.
timeout · actionLabel · onAction
timeout은 자동으로 닫히기까지의 시간입니다. 사용자가 조치를 취해야 하는 Toast에는 timeout: 0을 주어 자동으로 닫히지 않게 하세요. actionLabel과 onAction은 Toast 안에 버튼 하나를 붙입니다.
update와 promise
add가 돌려준 id로 update를 부르면 그 Toast를 제자리에서 갱신하고 타이머를 다시 시작합니다. "업로드 중 → 업로드 완료"처럼 하나의 Toast가 상태를 바꾸는 경우에 씁니다.
const toast = useToast();
const id = toast.add({
title: '삭제됨',
timeout: 0,
actionLabel: '실행 취소',
onAction: () => restore(id)
});
toast.update(id, { color: 'success', title: '복구됨' });promise는 같은 흐름을 Promise 하나로 처리합니다. 대기 · 성공 · 실패 메시지를 넘기면 Toast 하나가 상태에 따라 바뀝니다.
classNames
ToastProvider는 자기 요소를 그리지 않습니다. app을 감싸고 portal된 스택을 페이지에 놓을 뿐이라, 여기에는 className이 없고 그것이 붙을 root slot도 없습니다. 대신 스택의 각 파트에 이름이 있습니다.
<ToastProvider classNames={{ viewport: 'p-8', toast: 'font-mono' }}>
<App />
</ToastProvider>slot은 viewport, toast, title, description, action, close입니다. viewport는 toast가 쌓이는 띠이고 toast는 그중 하나로, 스택의 모든 toast에 적용됩니다. 넘긴 class가 컴포넌트 자신의 class와 어떻게 겨루는지는 prop 규약을 보세요.
Toast와 Alert 중 무엇을 쓸지
Alert는 해당 페이지에 속하며 그 자리에 남습니다. Toast는 방금 일어난 일을 알리고 사라집니다. 1분 뒤에도 여전히 유효한 메시지라면 Alert를 쓰세요.
접근성
- live region으로 전달되므로 갑자기 나타난 메시지도 screen reader에 읽힙니다.
priority: 'high'는 screen reader가 읽던 내용을 끊고, 기본값은 끊기지 않고 기다립니다.- 타이머는 hover 중이거나 창이 비활성일 때 멈춥니다. F6으로 스택에 focus를 옮길 수 있습니다.
- 닫기 버튼은 스택이 hover되거나 focus를 받기 전까지 접근성 트리에서 빠져 있어, Toast가 "메시지 + 버튼"이 아니라 하나의 메시지로 읽힙니다.
- 모든 toast의 × 이름은 provider의
locale이 정합니다.closeLabel로 직접 쓸 수도 있습니다.