Toast
Interactive UI Explorer
아래에서 각 컴포넌트의 다양한 Variants와 States를 문서화된 형태로 테스트해볼 수 있습니다.
Live Preview
저장되었습니다
!변경사항이 있습니다
×전송 실패 — 다시 시도해 주세요
Toast
화면 하단에서 위로 올라오는 애니메이션이 적용된 토스트 팝업입니다.
첫 번째 토스트가 나타날 때 컨테이너가 생성되고, 이후에는 그 안에 순서대로 쌓입니다.
각 토스트는 duration(초) 이후 자동으로 사라지며, 지정하지 않으면 기본 3초 동안 유지됩니다.
기본 토스트 (3초 유지)
duration을 지정하지 않으면 기본 3초 동안 표시됩니다.
Source Code
Live Preview
용도별 유지 시간 (duration)
duration(초)을 props로 넘겨 상황에 맞는 유지 시간을 가질 수 있습니다.
5 Seconds
Live Preview
1 Second
Live Preview
상황별 Variant 토스트
성공 / 에러 / 경고 / 정보 등 상황에 따라 색상과 아이콘이 달라지는 토스트입니다.
Success
Live Preview
Error
Live Preview
Warning
Live Preview
Info
Live Preview
클릭 가능한 토스트
토스트를 클릭했을 때 추가 동작(onClick)을 수행할 수 있습니다.
Source Code
Live Preview
Implementation
제작 코드
이 컴포넌트가 실제로 어떻게 구현되어 있는지 — 본체 .tsx 파일을 그대로 보여줍니다. variant 매핑·접근성 처리·forwardRef 패턴 등 디테일을 그대로 확인할 수 있어요.
Toasttypescript
'use client';
import { useEffect, useState, type ReactNode } from 'react';
import { cn } from '@/lib/cn';
export type ToastVariant = 'success' | 'error' | 'warning' | 'info' | 'default';
export interface ToastItem {
id: number;
message: ReactNode;
duration?: number; // 초 단위
onClick?: () => void;
variant?: ToastVariant;
}
export interface ToastStackProps {
toasts: ToastItem[];
onRemove: (id: number) => void;
defaultDurationSeconds?: number;
}
const DEFAULT_DURATION_SECONDS = 3;
interface ToastRowProps {
item: ToastItem;
index: number;
defaultDurationSeconds: number;
onRemove: (id: number) => void;
}
/**
* Variant별 아이콘 원형 배경 색.
* LIVE PREVIEW 스타일: slate-900 다크 패널 + 컬러 아이콘 원 + 흰 텍스트.
*/
const variantIconBg: Record<ToastVariant, string> = {
default: 'bg-slate-500',
info: 'bg-sky-500',
success: 'bg-emerald-500',
warning: 'bg-amber-500',
error: 'bg-rose-500',
};
function ToastVariantIcon({ variant, className }: { variant: ToastVariant; className?: string }) {
const c = cn('size-3.5 text-white', className);
switch (variant) {
case 'success':
return (
<svg className={c} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
);
case 'warning':
return (
<svg className={c} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v4m0 4h.01" />
</svg>
);
case 'error':
return (
<svg className={c} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
);
case 'info':
case 'default':
default:
return (
<svg className={c} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
);
}
}
function ToastRow({
item,
index,
defaultDurationSeconds,
onRemove,
}: ToastRowProps) {
const [leaving, setLeaving] = useState(false);
const variant = item.variant ?? 'default';
const iconBg = variantIconBg[variant];
useEffect(() => {
const seconds = item.duration ?? defaultDurationSeconds;
const timer = setTimeout(() => setLeaving(true), seconds * 1000);
return () => clearTimeout(timer);
}, [item.duration, defaultDurationSeconds]);
useEffect(() => {
if (!leaving) return;
const timer = setTimeout(() => onRemove(item.id), 200);
return () => clearTimeout(timer);
}, [leaving, item.id, onRemove]);
return (
<div
className={cn(
'sg-toast sg-toast-stack-item w-full min-w-[280px] flex items-center gap-3 rounded-xl bg-slate-900 px-4 py-3 shadow-xl shadow-slate-900/30 transition-all duration-200',
index > 0 && 'sg-toast-stacked',
leaving && 'sg-toast-leave',
item.onClick && 'cursor-pointer hover:bg-slate-800 active:scale-[0.99]',
)}
role="status"
onClick={item.onClick}
>
<span
className={cn(
'flex size-6 shrink-0 items-center justify-center rounded-full',
iconBg,
)}
>
<ToastVariantIcon variant={variant} />
</span>
<div className="min-w-0 flex-1 text-sm font-semibold leading-snug text-white">
{typeof item.message === 'string' ? item.message : <>{item.message}</>}
</div>
</div>
);
}
/**
* 화면 하단에 쌓이는 토스트 스택 컴포넌트
*
* - Alert/Confirm과 동일한 variant·아이콘·색상 체계로 일관된 디자인
* - 첫 번째 토스트가 나타날 때 아래에서 위로 올라오는 애니메이션
* - 각 토스트는 duration(초) 이후 자동 제거
*/
export function ToastStack({
toasts,
onRemove,
defaultDurationSeconds = DEFAULT_DURATION_SECONDS,
}: ToastStackProps) {
if (!toasts.length) return null;
return (
<div className="fixed bottom-8 left-1/2 z-130 w-full max-w-[420px] -translate-x-1/2 px-4">
<div className="sg-toast-stack flex w-full flex-col gap-3">
{toasts.map((item, index) => (
<ToastRow
key={item.id}
item={item}
index={index}
defaultDurationSeconds={defaultDurationSeconds}
onRemove={onRemove}
/>
))}
</div>
</div>
);
}