Alert

Inline alert banners for feedback messages — info, success, warning, and danger variants with icons, accent borders, links, actions, and dismissible support.

Preview

Basic alert

import { Alert, AlertTitle, AlertDescription } from '@/registry/ui-kit/tailwind-alert/react'

function Example() {
  return (
    <Alert variant="info">
      <AlertTitle>New update available</AlertTitle>
      <AlertDescription>A new software update is available for download.</AlertDescription>
    </Alert>
  )
}

All variants

import { Alert, AlertDescription } from '@/registry/ui-kit/tailwind-alert/react'

function Example() {
  return (
    <div className="space-y-3">
      <Alert variant="info">
        <AlertDescription>This is an informational message.</AlertDescription>
      </Alert>
      <Alert variant="success">
        <AlertDescription>Changes saved successfully.</AlertDescription>
      </Alert>
      <Alert variant="warning">
        <AlertDescription>You are approaching the rate limit.</AlertDescription>
      </Alert>
      <Alert variant="danger">
        <AlertDescription>Something went wrong. Please try again.</AlertDescription>
      </Alert>
    </div>
  )
}

Accent border

import { Alert, AlertTitle, AlertDescription } from '@/registry/ui-kit/tailwind-alert/react'

function Example() {
  return (
    <Alert variant="success" accent>
      <AlertTitle>Deployment complete</AlertTitle>
      <AlertDescription>Your application has been deployed to production.</AlertDescription>
    </Alert>
  )
}

With link

import { Alert, AlertDescription, AlertLink } from '@/registry/ui-kit/tailwind-alert/react'

function Example() {
  return (
    <Alert variant="warning">
      <AlertDescription>
        Your subscription expires in 3 days.{' '}
        <AlertLink variant="warning" href="/billing">Renew now</AlertLink>
      </AlertDescription>
    </Alert>
  )
}

With actions

import { Alert, AlertTitle, AlertDescription, AlertActions } from '@/registry/ui-kit/tailwind-alert/react'

function Example() {
  return (
    <Alert variant="danger">
      <AlertTitle>Delete workspace?</AlertTitle>
      <AlertDescription>This action cannot be undone.</AlertDescription>
      <AlertActions>
        <button className="rounded-md bg-rose-600 px-3 py-1.5 text-xs font-medium text-white">
          Delete
        </button>
        <button className="text-xs font-medium text-rose-700">Cancel</button>
      </AlertActions>
    </Alert>
  )
}

Dismissible

import { Alert, AlertTitle, AlertDescription } from '@/registry/ui-kit/tailwind-alert/react'

function Example() {
  return (
    <Alert variant="info" dismissible onDismiss={() => console.log('dismissed')}>
      <AlertTitle>Heads up!</AlertTitle>
      <AlertDescription>Click the X button to dismiss this alert.</AlertDescription>
    </Alert>
  )
}

Component API

PropDefaultDescription
variant"info"Visual style of the alert.
iconautoCustom icon element. Pass null to hide the icon. Defaults to a variant-matched icon.
accentfalseShow a colored left accent border.
dismissiblefalseShow a close button that removes the alert.
onDismissCallback fired when the alert is dismissed.
AlertTitleBold heading text inside the alert.
AlertDescriptionBody text inside the alert.
AlertActionsWrapper for action buttons below the description.
AlertLinkStyled inline link. Accepts a variant prop for color matching.

Source Code

"use client";

import { forwardRef, useState } from "react";

/* ── Variant styles ── */

const variantStyles = {
  info: {
    container: "border-sky-200 bg-sky-50 dark:border-sky-500/30 dark:bg-sky-950/40",
    accent: "bg-sky-500",
    icon: "text-sky-600 dark:text-sky-400",
    title: "text-sky-900 dark:text-sky-100",
    description: "text-sky-800 dark:text-sky-200",
    close: "text-sky-500 hover:bg-sky-100 dark:hover:bg-sky-900/40",
    link: "text-sky-700 underline decoration-sky-700/30 hover:decoration-sky-700 dark:text-sky-300 dark:decoration-sky-300/30 dark:hover:decoration-sky-300",
  },
  success: {
    container: "border-emerald-200 bg-emerald-50 dark:border-emerald-500/30 dark:bg-emerald-950/40",
    accent: "bg-emerald-500",
    icon: "text-emerald-600 dark:text-emerald-400",
    title: "text-emerald-900 dark:text-emerald-100",
    description: "text-emerald-800 dark:text-emerald-200",
    close: "text-emerald-500 hover:bg-emerald-100 dark:hover:bg-emerald-900/40",
    link: "text-emerald-700 underline decoration-emerald-700/30 hover:decoration-emerald-700 dark:text-emerald-300 dark:decoration-emerald-300/30 dark:hover:decoration-emerald-300",
  },
  warning: {
    container: "border-amber-200 bg-amber-50 dark:border-amber-500/30 dark:bg-amber-950/40",
    accent: "bg-amber-500",
    icon: "text-amber-600 dark:text-amber-400",
    title: "text-amber-900 dark:text-amber-100",
    description: "text-amber-800 dark:text-amber-200",
    close: "text-amber-500 hover:bg-amber-100 dark:hover:bg-amber-900/40",
    link: "text-amber-700 underline decoration-amber-700/30 hover:decoration-amber-700 dark:text-amber-300 dark:decoration-amber-300/30 dark:hover:decoration-amber-300",
  },
  danger: {
    container: "border-rose-200 bg-rose-50 dark:border-rose-500/30 dark:bg-rose-950/40",
    accent: "bg-rose-500",
    icon: "text-rose-600 dark:text-rose-400",
    title: "text-rose-900 dark:text-rose-100",
    description: "text-rose-800 dark:text-rose-200",
    close: "text-rose-500 hover:bg-rose-100 dark:hover:bg-rose-900/40",
    link: "text-rose-700 underline decoration-rose-700/30 hover:decoration-rose-700 dark:text-rose-300 dark:decoration-rose-300/30 dark:hover:decoration-rose-300",
  },
};

type AlertVariant = keyof typeof variantStyles;

/* ── Icons ── */

function InfoIcon({ className }: { className?: string }) {
  return (
    <svg className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <circle cx="12" cy="12" r="10" />
      <path d="M12 16v-4" />
      <path d="M12 8h.01" />
    </svg>
  );
}

function CheckCircleIcon({ className }: { className?: string }) {
  return (
    <svg className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
      <path d="m9 11 3 3L22 4" />
    </svg>
  );
}

function TriangleAlertIcon({ className }: { className?: string }) {
  return (
    <svg className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
      <path d="M12 9v4" />
      <path d="M12 17h.01" />
    </svg>
  );
}

function XCircleIcon({ className }: { className?: string }) {
  return (
    <svg className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <circle cx="12" cy="12" r="10" />
      <path d="m15 9-6 6" />
      <path d="m9 9 6 6" />
    </svg>
  );
}

const defaultIcons: Record<AlertVariant, React.ComponentType<{ className?: string }>> = {
  info: InfoIcon,
  success: CheckCircleIcon,
  warning: TriangleAlertIcon,
  danger: XCircleIcon,
};

/* ── Alert ── */

interface AlertProps {
  variant?: AlertVariant;
  icon?: React.ReactNode;
  accent?: boolean;
  dismissible?: boolean;
  onDismiss?: () => void;
  children: React.ReactNode;
  className?: string;
}

const Alert = forwardRef<HTMLDivElement, AlertProps>(
  ({ variant = "info", icon, accent = false, dismissible = false, onDismiss, children, className = "" }, ref) => {
    const [visible, setVisible] = useState(true);
    const styles = variantStyles[variant];
    const DefaultIcon = defaultIcons[variant];

    if (!visible) return null;

    const handleDismiss = () => {
      setVisible(false);
      onDismiss?.();
    };

    const resolvedIcon = icon !== undefined ? icon : <DefaultIcon className={`h-5 w-5 shrink-0 ${styles.icon}`} />;

    return (
      <div
        ref={ref}
        role="alert"
        className={`relative flex gap-3 overflow-hidden rounded-lg border p-4 ${styles.container} ${className}`}
      >
        {accent && (
          <div className={`absolute inset-y-0 left-0 w-1 ${styles.accent}`} />
        )}
        <div className={accent ? "pl-1" : ""}>
          {resolvedIcon}
        </div>
        <div className="flex-1 space-y-1">
          {children}
        </div>
        {dismissible && (
          <button
            type="button"
            onClick={handleDismiss}
            className={`-mr-1 -mt-1 inline-flex shrink-0 rounded-md p-1 transition-colors ${styles.close}`}
            aria-label="Dismiss"
          >
            <svg className="h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M18 6 6 18" />
              <path d="m6 6 12 12" />
            </svg>
          </button>
        )}
      </div>
    );
  }
);
Alert.displayName = "Alert";

/* ── AlertTitle ── */

function AlertTitle({ children, className = "" }: { children: React.ReactNode; className?: string }) {
  return <h5 className={`text-sm font-medium ${className}`}>{children}</h5>;
}

/* ── AlertDescription ── */

function AlertDescription({ children, className = "" }: { children: React.ReactNode; className?: string }) {
  return <p className={`text-sm ${className}`}>{children}</p>;
}

/* ── AlertActions ── */

function AlertActions({ children, className = "" }: { children: React.ReactNode; className?: string }) {
  return <div className={`mt-2 flex items-center gap-3 ${className}`}>{children}</div>;
}

/* ── AlertLink ── */

interface AlertLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
  variant?: AlertVariant;
  children: React.ReactNode;
}

function AlertLink({ variant = "info", children, className = "", ...props }: AlertLinkProps) {
  const styles = variantStyles[variant];
  return (
    <a className={`font-medium transition-colors ${styles.link} ${className}`} {...props}>
      {children}
    </a>
  );
}

export { Alert, AlertTitle, AlertDescription, AlertActions, AlertLink };
export type { AlertVariant };
export default Alert;