Alert
Free · MITSoft, solid, outline, left-accent, glass, neo and full-width banner alerts with tone-picked icons, actions, slide, fade, shake or pop entrances, smooth collapse on dismiss and a pausable auto-dismiss countdown.
Installation
$ npx shadcn@latest add https://goodui.dev/r/alert.jsonThe file lands in components/goodui/alert.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/alert works too.
Usage
import { Alert } from "@/components/goodui/alert"
import { Button } from "@/components/goodui/button"
export function PaymentFailed() {
return (
<Alert
tone="danger"
title="Your last payment failed"
animation="slide-down"
dismissible
actions={
<>
<Button size="sm" tone="danger">
Update card
</Button>
<Button size="sm" variant="ghost" tone="neutral">
View invoice
</Button>
</>
}
>
We could not charge the Visa ending in 4242 for $29.00. Online bookings pause in 3 days.
</Alert>
)
}When to use
- A message tied to one page or section: a failed payment, an expiring trial, a sync that needs attention.
- Form-level errors above a form ("We could not save your changes") with a Retry action.
- A full-width announcement bar at the top of an app: variant="banner".
- Short confirmations that go away on their own: autoDismiss with a countdown bar that pauses on hover.
- Feedback that floats above the page after an action ("Booking saved"): use toast-stack.
- An error on one field: use the state and message props of input.
- A question that needs an answer before continuing: use a dialog such as morphing-dialog.
| If you need | Use |
|---|---|
| Inline message in a page or card | variant="soft" (default) with a tone |
| Critical problem the user must notice | variant="solid" tone="danger", animation="shake" when it appears after an action |
| Quiet note inside a dense settings page | variant="left-accent" or "outline", size="sm" |
| Site-wide announcement or trial notice | variant="banner" with actions and dismissible |
| Confirmation that clears itself | autoDismiss (5 s) or autoDismiss={8000}, tone="success" |
| Over a photo or gradient hero | variant="glass" |
Examples
Announcement banner
<Alert variant="banner" tone="primary" dismissible actions={<a href="/billing" className="font-semibold underline">Upgrade</a>}>
Your trial ends in 3 days. Keep online booking and SMS reminders.
</Alert>Auto-dismissing confirmation
<Alert tone="success" autoDismiss={6000} animation="pop" title="Reminder sent" onDismiss={() => setSent(false)}>
Maya gets an SMS 24 hours before her 09:30 appointment.
</Alert>Controlled
const [open, setOpen] = React.useState(true)
<Alert open={open} onOpenChange={setOpen} dismissible tone="warning" title="Calendar sync paused">
Reconnect Google Calendar to keep availability up to date.
</Alert>API reference
| Prop | Type | Default |
|---|---|---|
| variant soft (default), solid, outline, left-accent stripe, frosted glass, neo-brutalist, or banner (full-width bar, centered, ignores size and shape). | "soft" | "solid" | "outline" | "left-accent" | "glass" | "neo" | "banner" | "soft" |
| tone Color and default icon. danger and warning use role="alert", the rest role="status". | "primary" | "neutral" | "success" | "warning" | "danger" | "info" | "info" |
| shadow Drop shadow. Ignored by neo, which has its own offset shadow. | "none" | "sm" | "md" | "lg" | "none" |
| animation Entrance animation, played on mount. | "none" | "slide-down" | "fade" | "shake" | "pop" | "none" |
| shape | "square" | "rounded" | "pill" | "rounded" |
| size | "sm" | "md" | "lg" | "md" |
| title Bold first line; children render as the description under it. | React.ReactNode | — |
| icon Replaces the tone's icon. | React.ReactNode | — |
| hideIcon Hides the icon entirely. | boolean | — |
| actions Buttons or links, shown under the text (inline on the banner). | React.ReactNode | — |
| dismissible Shows a × button. | boolean | — |
| autoDismiss Close automatically after this many ms (true = 5000), with a countdown bar. Pauses on hover and focus. | boolean | number | — |
| open Controlled visibility. | boolean | — |
| defaultOpen Initial visibility when uncontrolled. | boolean | true |
| onOpenChange Called with false when the × button or the auto-dismiss timer closes it. | (open: boolean) => void | — |
| onDismiss Called after the collapse animation finishes. | () => void | — |
| fullWidth false shrinks the alert to its content. | boolean | true |
| labels Text for built-in controls and screen readers; pass translations here. | Partial<AlertLabels> | — |
Also accepts every <div> attribute.
Accessibility
| Tab | Focusing anything inside the alert (an action or the × button) pauses autoDismiss until focus leaves. |
| Enter / Space | Activates the × button (native <button>). |
- tone="danger" and "warning" render role="alert" (announced immediately); other tones render role="status".
- The tone icon is aria-hidden; the title and text carry the meaning.
- The × button has aria-label "Dismiss" (labels.dismiss). The countdown bar is aria-hidden.
- autoDismiss pauses while the pointer is over the alert or focus is inside it, so people can read it and reach its actions.
Motion
- Entrance on mount: slide-down drops in 10px and fades in 350 ms, fade fades in 350 ms, pop scales from 0.94 with overshoot in 400 ms, shake shakes sideways for 500 ms.
- Dismiss: fades out in 180 ms while the height collapses over 280 ms (after an 80 ms delay), so content below slides up; onDismiss fires when it finishes.
- autoDismiss: a 3px bar shrinks linearly over the duration and freezes while paused.
- Reduced motion: no entrance animation and no transitions, the collapse is instant and the countdown bar is hidden (the timer still runs).
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- Default is variant="soft" tone="info". tone picks the icon too (primary Sparkles, neutral Bell, success CircleCheck, warning TriangleAlert, danger CircleAlert, info Info); override with icon or hideIcon.
- children is the description; title is the bold line. Put buttons in actions, not in children.
- Uncontrolled alerts hide themselves on dismiss and come back when the string title or text changes. Use open and onOpenChange when the parent decides.
- autoDismiss={true} means 5000 ms; a number is milliseconds. It needs no dismissible to work.
- variant="banner" centers its content, shows actions inline and ignores size and shape. labels={{ dismiss: "Schließen" }} translates the × button.
FAQ
What is the difference between onOpenChange and onDismiss?
onOpenChange(false) fires the moment the alert is closed (× or timer). onDismiss fires after the collapse animation, when the alert has left the layout.
Can the banner stick to the top of the page?
Yes. Wrap it in your own sticky container (for example <div className="sticky top-0 z-50">); the alert itself is not positioned.
Related
Self-contained toaster with toast() API: cards stack and expand on hover, swipe away with a drag, morph from loading to success or error for promises, and pause their timers on hover.
One badge, every layer: solid, soft, outline, status dot, gradient, glass and neo in six tones, with pulse, shine, bounce-in and count-pop animations, icons, removable chips and clickable filters.
One button with ten variants, six tones, seven shadows, ten animations, three shapes and five sizes that all combine, plus loading and icon slots.
Six field styles with top, floating, inline or hidden labels, focus ring, glow, underline-grow or lift, error shake with sliding messages, icon and text addons, clear button, counter and auto-growing textarea.