# Alert

> Inline alert with info, success, warning and error variants, optional title, dismiss button and action.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/alert
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/alert.json
```

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

```bash
npm install lucide-react
```

## Usage

```tsx title="components/alert-demo.tsx"
"use client"

import { Alert } from "@/components/alert"

const actionBtn =
  "inline-flex h-8 items-center rounded-lg border bg-background px-3 text-xs font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring/40"

export default function AlertDemo() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-3">
      <Alert variant="info" title="Scheduled maintenance" description="The dashboard will be read-only on Sunday from 02:00 to 03:00 UTC." />
      <Alert
        variant="success"
        title="Deployment succeeded"
        description="Version 2.4.1 is live in all regions."
        dismissible
        action={<button type="button" className={actionBtn}>View logs</button>}
      />
      <Alert variant="warning" title="Storage almost full" description="You have used 92% of your plan. Upgrade to avoid upload failures." dismissible />
      <Alert
        variant="error"
        title="Payment failed"
        description="Your card was declined. Update your billing details to keep your subscription."
        action={<button type="button" className={actionBtn}>Update card</button>}
      />
      <Alert variant="info" description="Description-only alerts work too." />
    </div>
  )
}
```

## Source

```tsx title="components/alert.tsx"
"use client"

import * as React from "react"
import { CircleAlert, CircleCheck, Info, TriangleAlert, X } from "lucide-react"

import { cn } from "@/lib/utils"

export type AlertVariant = "info" | "success" | "warning" | "error"

export interface AlertProps extends Omit<React.ComponentProps<"div">, "title"> {
  variant?: AlertVariant
  title?: React.ReactNode
  description?: React.ReactNode
  /** Shows a dismiss button. The alert hides itself and calls `onDismiss`. */
  dismissible?: boolean
  onDismiss?: () => void
  /** Optional action, e.g. a button or link, rendered under the text. */
  action?: React.ReactNode
}

const variants: Record<
  AlertVariant,
  { icon: React.ComponentType<{ className?: string }>; box: string; icon_: string }
> = {
  info: {
    icon: Info,
    box: "border-sky-500/30 bg-sky-500/10 text-sky-900 dark:text-sky-100",
    icon_: "text-sky-600 dark:text-sky-400",
  },
  success: {
    icon: CircleCheck,
    box: "border-emerald-500/30 bg-emerald-500/10 text-emerald-900 dark:text-emerald-100",
    icon_: "text-emerald-600 dark:text-emerald-400",
  },
  warning: {
    icon: TriangleAlert,
    box: "border-amber-500/30 bg-amber-500/10 text-amber-900 dark:text-amber-100",
    icon_: "text-amber-600 dark:text-amber-400",
  },
  error: {
    icon: CircleAlert,
    box: "border-destructive/30 bg-destructive/10 text-red-900 dark:text-red-100",
    icon_: "text-destructive",
  },
}

export function Alert({
  variant = "info",
  title,
  description,
  dismissible = false,
  onDismiss,
  action,
  className,
  children,
  ...props
}: AlertProps) {
  const [visible, setVisible] = React.useState(true)
  if (!visible) return null

  const { icon: Icon, box, icon_ } = variants[variant]
  const urgent = variant === "error" || variant === "warning"

  return (
    <div
      role={urgent ? "alert" : "status"}
      className={cn("relative flex w-full gap-3 rounded-xl border p-4 text-sm", box, className)}
      {...props}
    >
      <Icon className={cn("mt-0.5 size-5 shrink-0", icon_)} aria-hidden />
      <div className="min-w-0 flex-1">
        {title && <p className="font-medium leading-6">{title}</p>}
        {description && (
          <p className={cn("leading-relaxed opacity-80", title ? "mt-0.5" : "leading-6")}>{description}</p>
        )}
        {children}
        {action && <div className="mt-3 flex items-center gap-2">{action}</div>}
      </div>
      {dismissible && (
        <button
          type="button"
          aria-label="Dismiss"
          onClick={() => {
            setVisible(false)
            onDismiss?.()
          }}
          className="-m-1 flex size-7 shrink-0 items-center justify-center self-start rounded-md opacity-60 transition hover:bg-foreground/10 hover:opacity-100 focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring/40"
        >
          <X className="size-4" />
        </button>
      )}
    </div>
  )
}
```
