Toast

방금 일어난 일을 짧게 알리고 스스로 사라지는 메시지입니다. 토스트는 createToaster()로 만든 스토어에 들어 있어 어떤 코드에서든 띄울 수 있고, 앱 루트 가까이에 둔 Toast.Region 하나에 나타납니다. 영역은 렌더링된 자리의 테마를 따릅니다.

// Once, at module scope.
export const toaster = createToaster();

// Once, near the app root.
<Toast.Region toaster={toaster}>
	{(toast) => (
		<Toast.Root>
			<Toast.Icon>
				<YourIcon />
			</Toast.Icon>
			<Toast.Title>{toast.title}</Toast.Title>
			<Toast.Description>{toast.description}</Toast.Description>
			<Toast.Action onClick={undo}>Undo</Toast.Action>
			<Toast.Close>×</Toast.Close>
		</Toast.Root>
	)}
</Toast.Region>

// Anywhere, even outside React.
toaster.add({ tone: 'success', title: 'Changes saved' });

Toasts over a dialog

Toasts already showing moved in front of this dialog. New ones land there too.

toaster.add()는 토스트의 id를 돌려줍니다. 이 id를 update()에 넘기면 토스트가 보이는 동안 "Uploading"을 "Uploaded"로 바꾸듯 토스트를 바꿀 수 있고(작업을 기다리는 토스트에는 duration: Infinity를 주세요), dismiss()에 넘기면 닫힙니다. 토스터에 이미 있는 id로 다시 추가하면 그 토스트가 그 자리에서 바뀝니다. createToaster<YourFields>()에 타입을 주면 토스트마다 직접 정한 필드를 넣고 렌더 함수에서 읽을 수 있습니다.

Todae는 아이콘을 제공하지 않습니다. Toast.Icon은 Alert처럼 직접 사용하는 아이콘 세트의 아이콘을 넣는 슬롯입니다.

토스트와 모달 대화 상자

토스트는 영역이 만드는 popover="manual" 요소 안에 있으므로 페이지 위의 최상위 레이어(top layer)에 놓입니다. 모달 <dialog>도 최상위 레이어에 있으며, 자기 바깥의 모든 것을 inert로 만듭니다. 열린 대화 상자 바깥의 토스트는 위에 그려져 있어도 클릭하거나 포커스할 수 없고, 스크린 리더도 찾지 못합니다. 팝오버를 다시 보여 줘도 달라지지 않습니다.

그래서 Dialog나 BottomSheet가 열려 있는 동안 영역은 그 안으로 옮겨 가 다시 나타납니다. 대화 상자가 열리기 전에 뜬 토스트도, 열려 있는 동안 뜬 토스트도 대화 상자와 배경(backdrop) 앞에 놓이고 계속 쓸 수 있습니다. 대화 상자가 닫히면 영역은 원래 자리로 돌아갑니다. 모달이 열려 있는 동안에는 토스트가 그 안에 있으므로 모달의 테마를 따릅니다. 대화 상자가 겹쳐 있으면 맨 앞의 것을 따라갑니다.

영역은 Todae의 모달 스택으로 모달을 알아봅니다. Dialog와 BottomSheet는 스스로 스택에 들어갑니다. 직접 만든 모달 <dialog>라면 useModalLayer를 호출하세요. 스택에 없는 모달 아래에서는 토스트가 inert로 남습니다.

새 토스트가 뜰 때마다 영역을 다시 보여 주므로, 마지막 토스트 이후에 열린 Popover나 Menu보다 앞에 놓입니다. 토스트를 클릭하면 바깥 클릭으로 닫히는 팝오버가 닫히는데, 그 바깥의 어떤 클릭이든 마찬가지입니다.

레이아웃

layout="list"는 토스트를 하나씩 모두 보여 줍니다. layout="stack"은 토스트를 가장 새 토스트 뒤에 toast.peek만큼 보이게 겹쳐 두고, 포인터가 위에 있거나 키보드 포커스가 안에 있는 동안 펼칩니다. 가장 새 토스트는 placement가 가리키는 가장자리에 가장 가깝게 놓입니다.

limit은 한 번에 보여 줄 토스트 수를 정합니다. 그보다 많이 추가된 토스트는 차례를 기다렸다가 다른 토스트가 사라지면 나타납니다.

영역은 직접 스택 스타일을 쓸 수 있도록 각 li에 --toast-index(가장 새 토스트는 0)와 --toast-offset(앞에 놓인 토스트들의 높이)을, 목록에 --toast-front-height와 --toast-count를 설정합니다.

시간

토스트는 duration이 지나면 스스로 닫힙니다. createToaster()나 add()에서 따로 정하지 않으면 5000밀리초입니다. Infinity면 닫을 때까지 남아 있습니다. 포인터가 영역 위에 있는 동안, 키보드 포커스가 안에 있는 동안, 페이지가 숨겨진 동안에는 모든 타이머가 멈춥니다.

토스트는 누군가 닿기 전에 사라질 수 있으므로, 어떤 일을 할 수 있는 유일한 방법이 되어서는 안 됩니다. Undo를 다른 곳에도 두거나, 동작이 있는 토스트에는 duration: Infinity를 주세요. Toast.Action은 onClick 뒤에 토스트를 닫습니다. 열어 두려면 event.preventDefault()를 호출하세요. 동작으로 Dialog를 열면 토스트가 사라진 뒤 포커스가 돌아갈 곳이 없으므로, Dialog에 restoreFocusRef를 전달하세요.

키보드와 스크린 리더

  • F8은 토스트가 보이는 동안 영역으로 포커스를 옮기고, Tab으로 토스트 안으로 들어갑니다. hotkey="Alt+T"처럼 hotkey로 바꾸거나, hotkey={null}로 끌 수 있습니다.
  • 토스트에서 Escape를 누르면 뒤의 대화 상자가 아니라 그 토스트가 닫힙니다. 다른 토스트가 남아 있으면 포커스는 영역에 머물고, 남은 토스트가 없으면 포커스가 들어오기 전 자리로 돌아갑니다.
  • 토스트는 첫 토스트보다 먼저 있던 라이브 영역 안의 목록이므로, 스크린 리더가 새 토스트를 차분하게(polite) 한 번 읽어 줍니다. critical 토스트도 마찬가지입니다. 바로 끼어들어야 하는 메시지는 Alert에 넣으세요.
  • 영역의 이름은 "Notifications"이고, Toast.Close는 텍스트가 이름이 되지 않으면 "Dismiss"라는 이름을 가집니다. 둘 다 StringsProvider의 notifications, dismiss 문자열에서 오며, 설정하지 않으면 영어입니다. 비어 있지 않은 aria-label이 여전히 우선합니다.

언제 쓰나요

언제: 저장, 전송, 복사처럼 이미 끝난 동작을 확인하거나, 답이 필요 없는 백그라운드 이벤트를 알릴 때.

쓰지 않을 때: 해결할 때까지 남아 있어야 하는 메시지 → Alert. 필드의 오류 → ErrorMessage. 작업을 막는 선택 → Dialog. 토스트에는 텍스트와 버튼, 버튼의 팁만 넣고, 메뉴나 팝오버, 대화 상자는 넣지 마세요. 그 안에서 Escape를 누르면 토스트가 닫힙니다.

Props

Toast.Region props
이름타입필수기본값설명
toasterToaster<T>예—createToaster()로 만든 스토어.
children(toast: ToastRecord<T>) => React.ReactNode예—토스트 하나를 렌더링합니다. 보통 Toast.Root입니다.
placement"top-start" | "top-center" | "top-end" | "bottom-start" | "bottom-center" | "bottom-end"아니요"bottom-end"뷰포트의 모서리나 가장자리. 가장 새 토스트가 가장자리에 가장 가깝게 놓입니다.
layout"list" | "stack"아니요"list"list는 모든 토스트를 그대로 보여 줍니다. stack은 가장 새 토스트 뒤로 접어 두었다가, 포인터가 위에 있거나 키보드 포커스가 안에 있는 동안 펼칩니다.
limitnumber아니요3한 번에 보여 줄 토스트 수. 그 뒤에 추가된 토스트는 차례를 기다립니다.
hotkeystring | null아니요"F8"토스트로 포커스를 옮기는 키. 예: "F8", "Alt+T". 조합 키는 Alt(Option), Ctrl, Meta(Cmd), Shift입니다. null이면 끕니다.
aria-labelstring아니요"Notifications"영역 랜드마크의 이름. 기본값은 StringsProvider의 notifications 문자열입니다.
Toast.Root props
이름타입필수기본값설명
tone"info" | "success" | "warning" | "critical"아니요the toast's own tonetoast contract 토큰을 거쳐 color.status.<tone>에서 색을 가져옵니다.
childrenReact.ReactNode아니요—Toast 파트 또는 아무 콘텐츠.
Toast.Icon props
이름타입필수기본값설명
childrenReact.ReactNode아니요—직접 사용하는 아이콘 세트의 아이콘 그래픽.
Toast.Title props
이름타입필수기본값설명
childrenReact.ReactNode아니요—제목 텍스트.
Toast.Description props
이름타입필수기본값설명
childrenReact.ReactNode아니요—설명 콘텐츠.
Toast.Action props
이름타입필수기본값설명
variant"primary" | "secondary" | "ghost"아니요"secondary"Button 변형.
onClickReact.MouseEventHandler<HTMLButtonElement>아니요—동작을 실행합니다. 토스트를 열어 두려면 event.preventDefault()를 호출하세요.
Toast.Close props
이름타입필수기본값설명
aria-labelstring아니요"Dismiss" when its text, not counting aria-hidden or SVG parts, has fewer than two letters or digits접근 가능한 이름. 글자나 숫자가 두 개 이상인 텍스트가 있으면 그 텍스트가 버튼의 이름이 됩니다. 그렇지 않으면 StringsProvider의 dismiss 문자열이 이름이 되며, 영어로는 Dismiss입니다.
onClickReact.MouseEventHandler<HTMLButtonElement>아니요—토스트를 닫기 전에 실행됩니다. 열어 두려면 event.preventDefault()를 호출하세요.
createToaster props
이름타입필수기본값설명
durationnumber아니요5000토스트가 스스로 닫히기까지의 기본 밀리초. Infinity면 닫을 때까지 남아 있습니다.