PA08▸🧩 Compose · 互動組合▸通知
Toast / Notification
Toast 通知
難度 ★★★★★標籤 toast · stack · auto-dismiss3 min read
模式簡述
Toast 看似簡單,做到「好用」其實要顧很多細節:堆疊上限、auto-dismiss 時長、錯誤不要自動消失、 mobile 出現位置。這裡先做核心堆疊行為,正式用建議直接套 sonner。
尚無 toast
Claude Code Prompt
參考:PA08 toast 技術:堆疊定位 + 自動消失 具體行為: - 位置:右下角(mobile 改頂部) - 堆疊:最多 3 條,超過自動移除最舊的 - 視覺:堆疊時後面的縮小 + 上移(透視感) - success: 3 秒自動消失 - info: 4 秒 - error: **不自動消失**,使用者必須手動關 - 每個都有 × 可關閉 - 點 × 用 fade + slide-out 限制: - 錯誤千萬不要 auto-dismiss,使用者錯過會找不到原因 - 堆疊上限 3 條,多了會炸版面 - 不要堆同類訊息(重複「儲存中」就 dedupe)
完整程式碼
'use client'
import { useState, useCallback } from 'react'
type Toast = { id: number; variant: 'success'|'info'|'error'; title: string; desc?: string }
export function useToasts() {
const [toasts, setToasts] = useState<Toast[]>([])
const push = useCallback((t: Omit<Toast, 'id'>) => {
const id = Date.now()
setToasts(prev => [...prev.slice(-2), { ...t, id }])
if (t.variant !== 'error') {
setTimeout(() => {
setToasts(prev => prev.filter(x => x.id !== id))
}, t.variant === 'success' ? 3000 : 4000)
}
}, [])
return { toasts, push }
}
export function ToastStack({ toasts }: { toasts: Toast[] }) {
return (
<div className="fixed bottom-3 right-3 flex flex-col-reverse gap-1.5">
{toasts.map((t, i) => {
const offset = (toasts.length - 1 - i) * 6
const scale = 1 - (toasts.length - 1 - i) * 0.04
return (
<div
key={t.id}
style={{ transform: `translateY(-${offset}px) scale(${scale})` }}
>
{t.title}
<button>×</button>
</div>
)
})}
</div>
)
}何時用
- ✅ 操作成功 / 失敗的非阻擋回饋(儲存、複製、送出)
- ✅ 背景任務狀態(上傳完成、deploy 結束)
- ✅ 系統通知(連線斷開、新訊息)
何時別用
- ❌ 需要使用者決策(用 dialog / confirm)
- ❌ 關鍵錯誤訊息(用 banner / dialog,toast 太短暫)
- ❌ 表單欄位錯誤(inline 顯示,不要 toast)
我踩過的坑
-
錯誤 toast 自動消失:使用者轉頭 3 秒就沒了,後來不知道發生啥。 錯誤 toast 一定要使用者手動關。
-
堆疊上限沒設:點儲存 10 次,畫面變成 toast 山。設上限 3 條最舒服。
-
mobile 也放右下:iPhone safe area + tab bar 把 toast 擠到看不見。 mobile 改放頂部,跟通知欄習慣一致。
相關模式
- 暫無