Alert
Static inline message block with tones + icon (icon paired with colour, never colour alone).
import { Alert }from "@/components/ui"
Tones
Heads up
This is an informational callout.
Saved
Your changes have been saved.
Almost out of space
You’ve used 90% of your quota.
Payment failed
We couldn’t process your card.
Full source
The complete component, read from src/components/ui/alert.tsx.
alert.tsx
"use client";
import { useState, type ReactNode } from "react";
import { Info, CheckCircle2, AlertTriangle, XCircle, X, type LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";
type AlertTone = "info" | "success" | "warning" | "error";
const TONE: Record<AlertTone, { icon: LucideIcon; wrap: string; icon_: string }> = {
info: { icon: Info, wrap: "border-border bg-bg-secondary", icon_: "text-text-secondary" },
success: { icon: CheckCircle2, wrap: "border-accent-muted bg-accent-light", icon_: "text-accent" },
warning: { icon: AlertTriangle, wrap: "border-warning/30 bg-warning-light", icon_: "text-warning" },
error: { icon: XCircle, wrap: "border-error/30 bg-error-light", icon_: "text-error" },
};
interface AlertProps {
tone?: AlertTone;
title?: ReactNode;
children?: ReactNode;
dismissible?: boolean;
onDismiss?: () => void;
action?: ReactNode;
}
export function Alert({ tone = "info", title, children, dismissible, onDismiss, action }: AlertProps) {
const [open, setOpen] = useState(true);
const cfg = TONE[tone];
const Icon = cfg.icon;
if (!open) return null;
return (
<div
role={tone === "error" ? "alert" : "status"}
className={cn("flex items-start gap-3 rounded-lg border p-3.5", cfg.wrap)}
>
<Icon size={18} className={cn("mt-0.5 shrink-0", cfg.icon_)} aria-hidden />
<div className="min-w-0 flex-1">
{title && <p className="text-sm font-semibold text-text">{title}</p>}
{children && <div className={cn("text-sm text-text-secondary", title && "mt-0.5")}>{children}</div>}
{action && <div className="mt-2">{action}</div>}
</div>
{dismissible && (
<button
type="button"
onClick={() => { setOpen(false); onDismiss?.(); }}
aria-label="Dismiss"
className="-mr-1 -mt-1 shrink-0 rounded p-1 text-text-tertiary hover:text-text focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent"
>
<X size={15} aria-hidden />
</button>
)}
</div>
);
}Dependencies
To use Alert you also need the following. Copy these into your project alongside it.
Recommended folder structure
project structure
src/components/ui/ └─ alert.tsx src/lib/ └─ utils.ts