본문으로 건너뛰기

Toast

화면 한쪽에 잠깐 떠올랐다 사라지는 알림입니다. 사용자의 흐름을 끊지 않고 작업 결과를 전달할 때 씁니다.

tsx
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타입기본값설명
localestringBCP 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의 조합입니다. 화면 한복판을 세로로 가르는 스택은 만들 수 없습니다
timeoutnumber5000기본 유지 시간(ms). 0이면 닫을 때까지 남습니다
limitnumber3동시에 보이는 개수. 넘친 것은 버려지지 않고 스택이 빠지면 나타납니다
widthnumber | string380토스트 하나의 최대 너비
closeLabelstring× 버튼의 접근성 이름
classNamesNebaSlots<'viewport' | 'toast' | 'title' | 'description' | 'action' | 'close'>루트 뒤에 있는 각 파트의 class. 루트 자체는 className이 맡으므로 root 키는 없습니다

useToast().add(options)

Prop타입기본값설명
titleReactNode제목
descriptionReactNode아래 설명. 이것만 있으면 한 줄짜리 토스트입니다
color공통'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info'이 토스트만 다른 색 계열로
variant공통'solid' | 'outline' | 'text'이 토스트만 다른 표면으로
iconReactNode | false앞머리 글리프. 기본값은 color에 딸린 그림
timeoutnumber이 토스트의 유지 시간(ms). 0은 읽고 나서 조치가 필요한 메시지에 씁니다
priority'low' | 'high''low'high는 스크린 리더의 말을 끊습니다. 오류는 그럴 만하고 저장 완료는 아닙니다
actionLabelReactNode액션 버튼의 라벨. 넘기면 버튼이 생깁니다
onAction(event) => void액션 버튼을 눌렀을 때
idstring같은 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을 주어 자동으로 닫히지 않게 하세요. actionLabelonAction은 Toast 안에 버튼 하나를 붙입니다.

update와 promise

add가 돌려준 idupdate를 부르면 그 Toast를 제자리에서 갱신하고 타이머를 다시 시작합니다. "업로드 중 → 업로드 완료"처럼 하나의 Toast가 상태를 바꾸는 경우에 씁니다.

tsx
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도 없습니다. 대신 스택의 각 파트에 이름이 있습니다.

tsx
<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로 직접 쓸 수도 있습니다.

Released under the MIT License