Toast

Toast

일시적으로 표시되는 간결한 메시지 컴포넌트예요.

출처: 문서

본문

화면 한쪽에 잠깐 나타났다가 자동으로 닫히는 알림(토스트) 컴포넌트예요. 자동 닫힘, 호버/포커스/윈도우 블러 시 닫힘 일시정지, 뷰포트로 이동하는 핫키, 스와이프 제스처로 닫기 등을 지원해요.

Features

  • 자동으로 닫혀요.
  • 호버, 포커스, 윈도우 블러 시 닫힘을 일시정지해요.
  • 토스트 뷰포트로 점프하는 핫키 지원.
  • 스와이프 제스처로 닫기 지원.
  • 스와이프 제스처 애니메이션을 위한 CSS 변수 노출.
  • 제어(controlled)/비제어(uncontrolled) 방식 모두 지원.

Anatomy

컴포넌트를 임포트해요.

import { Toast } from "radix-ui";

export default () => (
  <Toast.Provider>
    <Toast.Root>
      <Toast.Title />

      <Toast.Description />

      <Toast.Action />

      <Toast.Close />
    </Toast.Root>

    <Toast.Viewport />
  </Toast.Provider>
);

API Reference

Provider

토스트와 토스트 뷰포트를 감싸는 프로바이더예요. 보통 애플리케이션 전체를 감싸요.

Prop Type Default
duration number 5000
label* string "Notification"
swipeDirection enum "right"
swipeThreshold number 50
announcerContainer Element document.body

Viewport

토스트가 나타나는 고정 영역이에요. 사용자는 핫키를 눌러 뷰포트로 이동할 수 있어요. 키보드 사용자를 위해 핫키의 발견 가능성(discoverability)을 보장하는 것은 여러분의 몫이에요.

Prop Type Default
asChild boolean false
hotkey string[] ["F8"]
label string "Notifications ({hotkey})"

Root

자동으로 닫히는 토스트예요. 사용자 응답을 받기 위해 계속 열어 두면 안 돼요.

Prop Type Default
asChild boolean false
type enum "foreground"
duration number No default value
defaultOpen boolean true
open boolean No default value
onOpenChange function No default value
onEscapeKeyDown function No default value
onPause function No default value
onResume function No default value
onSwipeStart function No default value
onSwipeMove function No default value
onSwipeEnd function No default value
onSwipeCancel function No default value
forceMount boolean No default value
Data attribute Values
[data-state] "open" | "closed"
[data-swipe] "start" | "move" | "cancel" | "end"
[data-swipe-direction] "up" | "down" | "left" | "right"
CSS Variable Description
--radix-toast-swipe-move-x The offset position of the toast when horizontally swiping
--radix-toast-swipe-move-y The offset position of the toast when vertically swiping
--radix-toast-swipe-end-x The offset end position of the toast after horizontally swiping
--radix-toast-swipe-end-y The offset end position of the toast after vertically swiping

Title

토스트의 선택적 제목이에요.

Prop Type Default
asChild boolean false

Description

토스트 메시지예요.

Prop Type Default
asChild boolean false

Action

시간 제한의 결과로 예상치 못한 부작용이 있는 작업을 사용자가 완료해야 하지 않도록, 무시해도 안전한 동작이에요.

사용자 응답을 받는 것이 꼭 필요하면, AlertDialog를 토스트처럼 스타일링해 뷰포트로 포털하세요.

Prop Type Default
asChild boolean false
altText* string No default value

Close

사용자가 duration이 지나기 전에 토스트를 닫을 수 있게 해 주는 버튼이에요.

Prop Type Default
asChild boolean false

Examples

커스텀 핫키

keycode.info의 각 키 event.code 값을 사용해 기본 핫키를 덮어써요.

<Toast.Provider>
  {/* ... */}

  <Toast.Viewport hotkey={["altKey", "KeyT"]} />
</Toast.Provider>

커스텀 기간

토스트의 duration을 커스터마이즈해 프로바이더 값을 덮어써요.

<Toast.Root duration={3000}>
  <Toast.Description>Saved!</Toast.Description>
</Toast.Root>

토스트 중복 표시

사용자가 버튼을 클릭할 때마다 토스트가 나타나야 한다면 state로 같은 토스트의 여러 인스턴스를 렌더링해요(아래 참고). 아니면 파트를 추상화해 나만의 imperative API를 만들 수도 있어요.

export default () => {
  const [savedCount, setSavedCount] = React.useState(0);

  return (
    <div>
      <form onSubmit={() => setSavedCount((count) => count + 1)}>
        {/* ... */}

        <button>save</button>
      </form>

      {Array.from({ length: savedCount }).map((_, index) => (
        <Toast.Root key={index}>
          <Toast.Description>Saved!</Toast.Description>
        </Toast.Root>
      ))}
    </div>
  );
};

스와이프 제스처 애니메이션

--radix-toast-swipe-move-[x|y], --radix-toast-swipe-end-[x|y] CSS 변수와 data-swipe="[start|move|cancel|end]" 속성을 결합해 스와이프로 닫는 애니메이션을 만들어요. 예시:

// index.jsx

import { Toast } from "radix-ui";

import "./styles.css";

export default () => (
  <Toast.Provider swipeDirection="right">
    <Toast.Root className="ToastRoot">...</Toast.Root>

    <Toast.Viewport />
  </Toast.Provider>
);
/* styles.css */

.ToastRoot[data-swipe="move"] {
  transform: translateX(var(--radix-toast-swipe-move-x));
}

.ToastRoot[data-swipe="cancel"] {
  transform: translateX(0);
  transition: transform 200ms ease-out;
}

.ToastRoot[data-swipe="end"] {
  animation: slideRight 100ms ease-out;
}

@keyframes slideRight {
  from {
    transform: translateX(var(--radix-toast-swipe-end-x));
  }
  to {
    transform: translateX(100%);
  }
}

Accessibility

aria-live 요구사항을 준수해요.

민감도(Sensitivity)

type prop으로 스크린 리더에 대한 토스트의 민감도를 제어해요.

사용자 동작의 결과로 생긴 토스트는 foreground를, 백그라운드 작업에서 생성된 토스트는 background를 사용해요.

Foreground

Foreground 토스트는 즉시 공지돼요. 보조 기술은 foreground 토스트가 나타나면 이전에 큐에 있던 메시지를 지우기로 선택할 수 있어요. 서로 다른 foreground 토스트를 동시에 쌓는 것은 피하세요.

Background

Background 토스트는 다음 적절한 기회에 공지돼요. 예를 들어 스크린 리더가 현재 문장 읽기를 마쳤을 때예요. 큐에 있던 메시지를 지우지 않아서, 사용자 상호작용에 응답할 때 과도하게 쓰면 스크린 리더 사용자에게 버벅이는 느낌을 줄 수 있어요.

<Toast.Root type="foreground">
  <Toast.Description>File removed successfully.</Toast.Description>

  <Toast.Close>Dismiss</Toast.Close>
</Toast.Root>

<Toast.Root type="background">
  <Toast.Description>We've just released Radix 1.0.</Toast.Description>

  <Toast.Close>Dismiss</Toast.Close>
</Toast.Root>

대체 동작

Action의 altText prop으로 스크린 리더 사용자에게 토스트를 처리하는 대체 방법을 안내해요.

사용자를 애플리케이션에서 영구적으로 처리할 수 있는 곳으로 안내하거나, 자신만의 커스텀 핫키 로직을 구현할 수 있어요. 후자를 구현한다면 foreground 타입으로 즉시 공지하고 duration을 늘려 충분한 시간을 주세요.

<Toast.Root type="background">
  <Toast.Title>Upgrade Available!</Toast.Title>

  <Toast.Description>We've just released Radix 1.0.</Toast.Description>

  <Toast.Action altText="Goto account settings to upgrade">
    Upgrade
  </Toast.Action>

  <Toast.Close>Dismiss</Toast.Close>
</Toast.Root>

<Toast.Root type="foreground" duration={10000}>
  <Toast.Description>File removed successfully.</Toast.Description>

  <Toast.Action altText="Undo (Alt+U)">
    Undo <kbd>Alt</kbd>+<kbd>U</kbd>
  </Toast.Action>

  <Toast.Close>Dismiss</Toast.Close>
</Toast.Root>

닫기 아이콘 버튼

아이콘(또는 폰트 아이콘)을 제공할 때 스크린 리더 사용자를 위해 올바르게 라벨링하는 것을 기억하세요.

<Toast.Root type="foreground">
  <Toast.Description>Saved!</Toast.Description>

  <Toast.Close aria-label="Close">
    <span aria-hidden>×</span>
  </Toast.Close>
</Toast.Root>

키보드 상호작용

Key Description
F8 Focuses toasts viewport.
Tab Moves focus to the next focusable element.
Shift + Tab Moves focus to the previous focusable element.
Space When focus is on a Toast.Action or Toast.Close, closes the toast.
Enter When focus is on a Toast.Action or Toast.Close, closes the toast.
Esc When focus is on a Toast, closes the toast.

Custom APIs

파트 추상화

primitive 파트를 자신의 컴포넌트로 추상화해 나만의 API를 만들어요.

사용법

import { Toast } from "./your-toast";

export default () => (
  <Toast title="Upgrade available" content="We've just released Radix 3.0!">
    <button onClick={handleUpgrade}>Upgrade</button>
  </Toast>
);

구현

// your-toast.jsx

import { Toast as ToastPrimitive } from "radix-ui";

export const Toast = ({ title, content, children, ...props }) => {
  return (
    <ToastPrimitive.Root {...props}>
      {title && <ToastPrimitive.Title>{title}</ToastPrimitive.Title>}

      <ToastPrimitive.Description>{content}</ToastPrimitive.Description>

      {children && <ToastPrimitive.Action asChild>{children}</ToastPrimitive.Action>}

      <ToastPrimitive.Close aria-label="Close">
        <span aria-hidden>×</span>
      </ToastPrimitive.Close>
    </ToastPrimitive.Root>
  );
};

Imperative API

원하면 토스트 중복 표시를 허용하는 나만의 imperative API를 만들어요.

사용법

import { Toast } from "./your-toast";

export default () => {
  const savedRef = React.useRef();

  return (
    <div>
      <form onSubmit={() => savedRef.current.publish()}>
        {/* ... */}

        <button>Save</button>
      </form>

      <Toast ref={savedRef}>Saved successfully!</Toast>
    </div>
  );
};

구현

// your-toast.jsx

import * as React from "react";

import { Toast as ToastPrimitive } from "radix-ui";

export const Toast = React.forwardRef((props, forwardedRef) => {
  const { children, ...toastProps } = props;

  const [count, setCount] = React.useState(0);

  React.useImperativeHandle(forwardedRef, () => ({
    publish: () => setCount((count) => count + 1),
  }));

  return (
    <>
      {Array.from({ length: count }).map((_, index) => (
        <ToastPrimitive.Root key={index} {...toastProps}>
          <ToastPrimitive.Description>{children}</ToastPrimitive.Description>

          <ToastPrimitive.Close>Dismiss</ToastPrimitive.Close>
        </ToastPrimitive.Root>
      ))}
    </>
  );
});

더 알아보기 (Learn more)

  • 사용자 응답이 꼭 필요한 알림은 Toast 대신 AlertDialog를 토스트처럼 스타일링해 쓰는 것이 권장돼요.
  • Toast는 Toast.Provider로 감싸야 하며, type(foreground/background)으로 스크린 리더 공지 시점을 조절할 수 있어요.