Toast
aqua/toastFreeComponent
Growl-style notifications: call toast() and an Alert slides in from the corner.
Live preview
live · rendered by the registry
Rendered by aqua on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://aqua.duca.dev/r/toast.jsonSource
1"use client"23import * as React from "react"4import { XIcon } from "lucide-react"56import { cn } from "@/lib/utils"7import { Alert, AlertDescription, AlertTitle } from "@/registry/aqua/ui/alert"89type ToastVariant = "default" | "warning" | "destructive"1011export type ToastOptions = {12 title: string13 description?: string14 variant?: ToastVariant15 duration?: number16}1718type ToastItem = ToastOptions & {19 id: number20 leaving: boolean21}2223let toastCount = 024let toasts: ToastItem[] = []25const listeners = new Set<(items: ToastItem[]) => void>()2627function emit() {28 for (const listener of listeners) listener(toasts)29}3031function subscribe(listener: (items: ToastItem[]) => void) {32 listeners.add(listener)33 return () => {34 listeners.delete(listener)35 }36}3738function getSnapshot() {39 return toasts40}4142function toast(options: ToastOptions) {43 const id = ++toastCount44 toasts = [...toasts, { duration: 5000, ...options, id, leaving: false }]45 emit()46 return id47}4849function dismissToast(id: number) {50 if (!toasts.some((item) => item.id === id && !item.leaving)) return51 toasts = toasts.map((item) =>52 item.id === id ? { ...item, leaving: true } : item53 )54 emit()55 setTimeout(() => {56 toasts = toasts.filter((item) => item.id !== id)57 emit()58 }, 200)59}6061function Toaster({ className }: { className?: string }) {62 const items = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot)63 const scheduled = React.useRef(new Set<number>())6465 React.useEffect(() => {66 for (const item of items) {67 if (item.leaving || scheduled.current.has(item.id)) continue68 scheduled.current.add(item.id)69 setTimeout(() => dismissToast(item.id), item.duration)70 }71 }, [items])7273 return (74 <div75 className={cn(76 "pointer-events-none fixed right-4 top-4 z-[100] flex w-80 max-w-[calc(100vw-2rem)] flex-col gap-2.5",77 className78 )}79 >80 {items.map((item) => (81 <Alert82 key={item.id}83 variant={item.variant}84 className={cn(85 "group pointer-events-auto relative shadow-[0_1px_3px_rgba(20,30,50,0.12),0_10px_28px_rgba(20,30,50,0.3)]",86 item.leaving87 ? "animate-out fade-out-0 slide-out-to-right-8 fill-mode-forwards duration-200"88 : "animate-in fade-in-0 slide-in-from-right-8 duration-300"89 )}90 >91 <AlertTitle>{item.title}</AlertTitle>92 {item.description ? (93// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from aqua
All 26 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Alertalert | aqua | Components | Free | 1 dep | |
| Avataravatar | aqua | Components | Free | 1 dep | |
| Badgebadge | aqua | Components | Free | 2 deps | |
| Buttonbutton | aqua | Components | Free | 2 deps | |
| Chat Bubblechat-bubble | aqua | Components | Free | no deps | |
| Checkboxcheckbox | aqua | Components | Free | 2 deps | |
| Code Blockcode-block | aqua | Components | Free | 2 deps · 2 files | |
| Cursorcursor | aqua | Components | Free | no deps |
