Alert
Persistent in-flow message anchored to page content with an icon, title, and description.
Overview
Alert is the in-flow feedback primitive: a persistent message anchored inside page flow, distinct from Toast (transient, floating) and a page-spanning banner. Use it to surface information that the user needs to read in context before acting on the surrounding UI - a form-wide error summary, a tip about a configuration, a confirmation that an async write has just completed.
The Alert API stays deliberately small. Variants ship as default and destructive only - convey intent (info, success, warning, danger) through a leading icon from lucide-react rather than introducing more variants. This keeps the visual surface consistent and the API minimal.
Preview
'use client';import { Alert, AlertDescription, AlertTitle } from '@gremorie/rx-feedback';import { CheckCircle2, Info, AlertTriangle } from 'lucide-react';export function AlertPreview() { return ( <div className="flex flex-col gap-3"> <Alert> <Info className="size-4" /> <AlertTitle>Heads up</AlertTitle> <AlertDescription> The registry rebuilds on every commit to the main branch. </AlertDescription> </Alert> <Alert variant="destructive"> <AlertTriangle className="size-4" /> <AlertTitle>Something went wrong</AlertTitle> <AlertDescription> Could not reach the upstream registry. Retry shortly. </AlertDescription> </Alert> <Alert> <CheckCircle2 className="size-4" /> <AlertTitle>Success</AlertTitle> <AlertDescription>Primitive added to your project.</AlertDescription> </Alert> </div> );}Anatomy
Alert bordered container; pass an icon as the first child to claim the leading column
├─ <icon> optional leading lucide-react glyph (Info, CheckCircle2, TriangleAlert, XCircle)
├─ AlertTitle the headline
└─ AlertDescription the body textInstallation
bash npx gremorie@latest add rx-alert bash pnpm dlx gremorie@latest add rx-alert bash yarn dlx gremorie@latest add rx-alert bash bunx --bun gremorie@latest add rx-alert Usage
import { Alert, AlertTitle, AlertDescription } from "@gremorie/rx-feedback";
import { Info } from "lucide-react";
export function RegistryNotice() {
return (
<Alert>
<Info className="size-4" />
<AlertTitle>Heads up</AlertTitle>
<AlertDescription>
The registry rebuilds on every commit to the main branch.
</AlertDescription>
</Alert>
);
}npx gremorie@latest add ng-alertimport { Component } from '@angular/core';
import { Alert, AlertTitle, AlertDescription } from '@gremorie/ng-feedback';
@Component({
selector: 'app-example',
standalone: true,
imports: [Alert, AlertTitle, AlertDescription],
template: `
<gr-alert>
<gr-alert-title>Heads up</gr-alert-title>
<gr-alert-description>
The registry rebuilds on every commit to the main branch.
</gr-alert-description>
</gr-alert>
`,
})
export class ExampleComponent {}API
<Alert>
The root. Renders a <div role="alert"> styled as a two-column grid: an optional 16 px icon slot on the left and a stack of title plus description on the right. The icon column collapses when no <svg> child is present.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "destructive" | "default" | Visual intent. default uses card surface tokens; destructive uses the destructive text color and tints the description. |
className | string | - | Extra classes merged after the variant classes. Use for spacing escape hatches, not for changing the surface treatment. |
...props | React.ComponentProps<"div"> | - | Standard div attributes. The role="alert" is set automatically; do not override it. |
<AlertTitle>
Renders a <div> for the headline. Sits in the second column of the grid, line-clamped to one line by default so a long title cannot push the icon out of alignment.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Extra classes merged after the title classes. |
...props | React.ComponentProps<"div"> | - | Standard div attributes. |
<AlertDescription>
Renders a <div> for the body. Inherits muted foreground on the default variant, switches to a tinted destructive color on the destructive variant via the data-slot selector.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Extra classes merged after the description classes. |
...props | React.ComponentProps<"div"> | - | Standard div attributes including nested <p> (which receives a relaxed leading by default). |
Composition
<Alert>is the root grid. Pick the variant first - destructive only when an action failed or a safety boundary is being crossed.- An icon (any
lucide-reactglyph atclassName="size-4") is the first child. The grid auto-detects the SVG and reserves the icon column. <AlertTitle>is the headline. Keep it short (one line); the line-clamp prevents accidental wraps.<AlertDescription>is the body. One sentence is ideal; two paragraphs is the upper limit before the message belongs in a Dialog instead.
The grid auto-collapses to a single column when no icon is provided, so a stripped-down alert (title plus description only) still renders cleanly.
Variations
Alert ships exactly two CVA variants: default and destructive. There is
no success, warning, or info variant. Express those intents through a
leading lucide-react icon on the default surface, as shown below - this
keeps the visual surface consistent and the API minimal.
Destructive
'use client';import { Alert, AlertDescription, AlertTitle } from '@gremorie/rx-feedback';import { XCircle } from 'lucide-react';export function AlertDestructivePreview() { return ( <Alert variant="destructive"> <XCircle className="size-4" /> <AlertTitle>Payment failed</AlertTitle> <AlertDescription> Your card was declined. Update the payment method and try again. </AlertDescription> </Alert> );}The only non-default variant. Use for failed writes, broken integrations, and safety boundaries. The tinted description keeps the body readable while still signalling severity.
Intent via icon
'use client';import { Alert, AlertDescription, AlertTitle } from '@gremorie/rx-feedback';import { AlertTriangle, CheckCircle2, Info } from 'lucide-react';export function AlertWithIconPreview() { return ( <div className="flex flex-col gap-3"> <Alert> <Info className="size-4" /> <AlertTitle>Informational</AlertTitle> <AlertDescription> The registry rebuilds on every commit to the main branch. </AlertDescription> </Alert> <Alert> <CheckCircle2 className="size-4" /> <AlertTitle>Success</AlertTitle> <AlertDescription>Primitive added to your project.</AlertDescription> </Alert> <Alert> <AlertTriangle className="size-4" /> <AlertTitle>Warning</AlertTitle> <AlertDescription> This action rewrites tokens already imported by other components. </AlertDescription> </Alert> </div> );}Informational, success, and warning intents all reuse the default variant and carry their meaning through the leading icon (Info, CheckCircle2, AlertTriangle). Reach for a dedicated colored surface only when you genuinely need destructive emphasis.
Title only
'use client';import { Alert, AlertTitle } from '@gremorie/rx-feedback';import { Info } from 'lucide-react';export function AlertTitleOnlyPreview() { return ( <div className="flex flex-col gap-3"> <Alert> <Info className="size-4" /> <AlertTitle>Changes saved automatically.</AlertTitle> </Alert> <Alert> <AlertTitle>Read-only mode is on.</AlertTitle> </Alert> </div> );}Omit <AlertDescription> for a single-line status message. The grid collapses cleanly with or without a leading icon, so a terse confirmation reads as one compact row.
Accessibility
- Role and announcement: the root carries
role="alert", so assistive tech announces the message as it appears in the DOM. Mount alerts conditionally on the event that produced them, not eagerly at page load. - Title and description coupling: title and description live in the same grid cell so they announce as one chunk; no need to wire
aria-describedbyby hand. - Icons are decorative: the leading icon carries no semantic load. The intent is conveyed by the variant and the title - the icon merely reinforces it for sighted users. Leaf it as plain SVG (no
aria-label). - Color is never the only channel: destructive intent ships with a dedicated title surface plus a recognizable icon, satisfying WCAG 1.4.1 Use of Color.
- No motion injected: the primitive has no enter or exit animation. Pair with a motion wrapper if you want a fade or slide on mount.