HextaUI

Alert

Inline messages for status and feedback, with a neutral surface, a colored icon and a dismiss that collapses smoothly.

Heads up
You can add components to your app using the CLI.
import { IconCircleX, IconTerminal2 } from "@tabler/icons-react"

import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"

export function AlertDemo() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-3">
      <Alert>
        <IconTerminal2 />
        <AlertTitle>Heads up</AlertTitle>
        <AlertDescription>
          You can add components to your app using the CLI.
        </AlertDescription>
      </Alert>
      <Alert variant="destructive">
        <IconCircleX />
        <AlertTitle>Payment failed</AlertTitle>
        <AlertDescription>
          Your card was declined. Update your billing details.
        </AlertDescription>
      </Alert>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/alert.json

Adds the component, the HextaUI theme tokens and any HextaUI components it depends on.

import {
  Alert,
  AlertAction,
  AlertClose,
  AlertDescription,
  AlertTitle,
} from "@/components/ui/alert"
<Alert>
  <IconTerminal2 />
  <AlertTitle>Heads up</AlertTitle>
  <AlertDescription>
    You can add components to your app using the CLI.
  </AlertDescription>
</Alert>
Alert
├── AlertTitle
├── AlertDescription
├── AlertAction
└── AlertClose

Variants

variant colors only the icon, so the surface stays neutral and status reads at a glance without shouting.

Heads up
You can add components to your app using the CLI.
Changes saved
Your profile is now visible to your team.
New version available
Reload the page to get the latest features.
import {
  IconAlertTriangle,
  IconCircleCheck,
  IconCircleX,
  IconInfoCircle,
  IconTerminal2,
} from "@tabler/icons-react"

import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"

const alerts = [
  {
    variant: "default",
    icon: IconTerminal2,
    title: "Heads up",
    description: "You can add components to your app using the CLI.",
  },
  {
    variant: "destructive",
    icon: IconCircleX,
    title: "Payment failed",
    description: "Your card was declined. Update your billing details.",
  },
  {
    variant: "success",
    icon: IconCircleCheck,
    title: "Changes saved",
    description: "Your profile is now visible to your team.",
  },
  {
    variant: "info",
    icon: IconInfoCircle,
    title: "New version available",
    description: "Reload the page to get the latest features.",
  },
  {
    variant: "warning",
    icon: IconAlertTriangle,
    title: "Trial ends in 3 days",
    description: "Add a payment method to keep your workspace active.",
  },
] as const

export function AlertVariants() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-3">
      {alerts.map(({ variant, icon: Icon, title, description }) => (
        <Alert key={variant} variant={variant}>
          <Icon />
          <AlertTitle>{title}</AlertTitle>
          <AlertDescription>{description}</AlertDescription>
        </Alert>
      ))}
    </div>
  )
}

Soft

appearance="soft" drops the border and tints the whole surface with the variant color.

Heads up
You can add components to your app using the CLI.
Changes saved
Your profile is now visible to your team.
New version available
Reload the page to get the latest features.
Message archived
import {
  IconAlertTriangle,
  IconCircleCheck,
  IconCircleX,
  IconInfoCircle,
  IconTerminal2,
} from "@tabler/icons-react"

import {
  Alert,
  AlertAction,
  AlertClose,
  AlertDescription,
  AlertTitle,
} from "@/components/ui/alert"
import { Button } from "@/components/ui/button"

const alerts = [
  {
    variant: "default",
    icon: IconTerminal2,
    title: "Heads up",
    description: "You can add components to your app using the CLI.",
  },
  {
    variant: "destructive",
    icon: IconCircleX,
    title: "Payment failed",
    description: "Your card was declined. Update your billing details.",
  },
  {
    variant: "success",
    icon: IconCircleCheck,
    title: "Changes saved",
    description: "Your profile is now visible to your team.",
  },
  {
    variant: "info",
    icon: IconInfoCircle,
    title: "New version available",
    description: "Reload the page to get the latest features.",
  },
  {
    variant: "warning",
    icon: IconAlertTriangle,
    title: "Trial ends in 3 days",
    description: "Add a payment method to keep your workspace active.",
  },
] as const

export function AlertSoft() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-3">
      {alerts.map(({ variant, icon: Icon, title, description }) => (
        <Alert key={variant} variant={variant} appearance="soft">
          <Icon />
          <AlertTitle>{title}</AlertTitle>
          <AlertDescription>{description}</AlertDescription>
        </Alert>
      ))}
      <Alert variant="success" appearance="soft">
        <IconCircleCheck />
        <AlertTitle>Message archived</AlertTitle>
        <AlertAction>
          <Button variant="ghost" size="xs">
            Undo
          </Button>
        </AlertAction>
        <AlertClose />
      </Alert>
    </div>
  )
}

Partial content

Title only, description only and no icon all line up without extra classes. The grid places each part in its own column.

Invite sent
Sessions expire after 30 days of inactivity.
No icon
The content starts at the padding.
import { IconCircleCheck, IconInfoCircle } from "@tabler/icons-react"

import {
  Alert,
  AlertClose,
  AlertDescription,
  AlertTitle,
} from "@/components/ui/alert"

export function AlertPartialContent() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-3">
      <Alert variant="success">
        <IconCircleCheck />
        <AlertTitle>Invite sent</AlertTitle>
        <AlertClose />
      </Alert>
      <Alert>
        <IconInfoCircle />
        <AlertDescription>
          Sessions expire after 30 days of inactivity.
        </AlertDescription>
      </Alert>
      <Alert>
        <AlertTitle>No icon</AlertTitle>
        <AlertDescription>The content starts at the padding.</AlertDescription>
      </Alert>
    </div>
  )
}

Actions

Put buttons in <AlertAction />. They align with the first line of the title, even when it wraps.

A new version of the dashboard is available with faster charts, saved filters and a redesigned sidebar
Reloading keeps your current filters.
Message archived
import { IconInfoCircle, IconTerminal2 } from "@tabler/icons-react"

import {
  Alert,
  AlertAction,
  AlertClose,
  AlertDescription,
  AlertTitle,
} from "@/components/ui/alert"
import { Button } from "@/components/ui/button"

export function AlertActions() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-3">
      <Alert variant="info">
        <IconInfoCircle />
        <AlertTitle>
          A new version of the dashboard is available with faster charts, saved
          filters and a redesigned sidebar
        </AlertTitle>
        <AlertDescription>
          Reloading keeps your current filters.
        </AlertDescription>
        <AlertAction>
          <Button size="xs">Reload</Button>
        </AlertAction>
      </Alert>
      <Alert>
        <IconTerminal2 />
        <AlertTitle>Message archived</AlertTitle>
        <AlertAction>
          <Button variant="secondary" size="xs">
            Undo
          </Button>
        </AlertAction>
        <AlertClose />
      </Alert>
    </div>
  )
}

Dismissible

Add <AlertClose /> to let people dismiss it. The alert collapses smoothly and the content below slides up. Dismiss it from the keyboard and focus moves to the next control, here “Reset”.

We’ve updated our terms
Read the new terms before your next billing date.
"use client"

import * as React from "react"
import { IconInfoCircle } from "@tabler/icons-react"

import {
  Alert,
  AlertClose,
  AlertDescription,
  AlertTitle,
} from "@/components/ui/alert"
import { Button } from "@/components/ui/button"

export function AlertDismissible() {
  const [key, setKey] = React.useState(0)

  return (
    <div className="flex w-full max-w-lg flex-col gap-3">
      <Alert key={key} variant="info">
        <IconInfoCircle />
        <AlertTitle>We’ve updated our terms</AlertTitle>
        <AlertDescription>
          Read the <a href="#">new terms</a> before your next billing date.
        </AlertDescription>
        <AlertClose />
      </Alert>
      <div className="flex gap-2">
        <Button variant="outline" size="sm" onClick={() => setKey(key + 1)}>
          Reset
        </Button>
        <Button variant="outline" size="sm">
          Next focusable
        </Button>
      </div>
    </div>
  )
}

Controlled

Pass open and onOpenChange to drive it from your own state. It animates both in and out.

Deployment complete
Your changes are live on production.

Content below slides up.

"use client"

import * as React from "react"
import { IconCircleCheck } from "@tabler/icons-react"

import {
  Alert,
  AlertClose,
  AlertDescription,
  AlertTitle,
} from "@/components/ui/alert"
import { Button } from "@/components/ui/button"

export function AlertControlled() {
  const [open, setOpen] = React.useState(true)

  return (
    <div className="flex w-full max-w-lg flex-col gap-3">
      <Button
        variant="outline"
        size="sm"
        className="self-start"
        onClick={() => setOpen(!open)}
      >
        {open ? "Hide" : "Show"} alert
      </Button>
      <Alert variant="success" open={open} onOpenChange={setOpen}>
        <IconCircleCheck />
        <AlertTitle>Deployment complete</AlertTitle>
        <AlertDescription>
          Your changes are live on production.
        </AlertDescription>
        <AlertClose />
      </Alert>
      <p className="text-sm text-muted-foreground">Content below slides up.</p>
    </div>
  )
}

Cancel a dismiss

Call event.preventDefault() in the close button’s onClick to keep the alert open, for example to ask for confirmation first.

"use client"

import * as React from "react"
import { IconAlertTriangle } from "@tabler/icons-react"

import {
  Alert,
  AlertClose,
  AlertDescription,
  AlertTitle,
} from "@/components/ui/alert"

export function AlertCancelDismiss() {
  const [confirmed, setConfirmed] = React.useState(false)

  return (
    <Alert variant="warning" className="max-w-lg">
      <IconAlertTriangle />
      <AlertTitle>Unsaved changes</AlertTitle>
      <AlertDescription>
        {confirmed
          ? "Click dismiss again to close."
          : "The first click is cancelled with event.preventDefault()."}
      </AlertDescription>
      <AlertClose
        onClick={(event) => {
          if (!confirmed) {
            event.preventDefault()
            setConfirmed(true)
          }
        }}
      />
    </Alert>
  )
}

Inserted after load

Alerts that mount after the page has loaded fade and slide in, and screen readers announce them. Alerts already in the server HTML appear without motion.

"use client"

import * as React from "react"
import { IconCircleX } from "@tabler/icons-react"

import {
  Alert,
  AlertClose,
  AlertDescription,
  AlertTitle,
} from "@/components/ui/alert"
import { Button } from "@/components/ui/button"

export function AlertInserted() {
  const [errors, setErrors] = React.useState<number[]>([])

  return (
    <div className="flex w-full max-w-lg flex-col gap-3">
      <Button
        variant="outline"
        size="sm"
        className="self-start"
        onClick={() => setErrors([...errors, Date.now()])}
      >
        Submit form
      </Button>
      {errors.map((id) => (
        <Alert
          key={id}
          variant="destructive"
          onOpenChange={() =>
            setErrors((current) => current.filter((error) => error !== id))
          }
        >
          <IconCircleX />
          <AlertTitle>Email is already in use</AlertTitle>
          <AlertDescription>Try signing in instead.</AlertDescription>
          <AlertClose />
        </Alert>
      ))}
    </div>
  )
}

Rich content

Inline icons and links inside the description keep their own style. Use render to give the title a heading level.

Rendered as an h3

Press to open the terminal, or read the docs.

Open guide
import {
  IconExternalLink,
  IconInfoCircle,
  IconTerminal2,
} from "@tabler/icons-react"

import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Button } from "@/components/ui/button"

export function AlertRichContent() {
  return (
    <Alert className="max-w-lg">
      <IconInfoCircle />
      <AlertTitle render={<h3 />}>Rendered as an h3</AlertTitle>
      <AlertDescription>
        <p>
          Press <IconTerminal2 className="inline size-4 align-text-bottom" /> to
          open the terminal, or read the <a href="#">docs</a>.
        </p>
        <Button
          variant="outline"
          size="sm"
          nativeButton={false}
          render={<a href="#" />}
        >
          Open guide <IconExternalLink />
        </Button>
      </AlertDescription>
    </Alert>
  )
}

Right to left

The icon, content and close button mirror with the reading direction.

import { IconAlertTriangle } from "@tabler/icons-react"

import {
  Alert,
  AlertClose,
  AlertDescription,
  AlertTitle,
} from "@/components/ui/alert"

export function AlertRtl() {
  return (
    <div dir="rtl" className="w-full max-w-lg">
      <Alert variant="warning">
        <IconAlertTriangle />
        <AlertTitle>تنتهي الفترة التجريبية خلال 3 أيام</AlertTitle>
        <AlertDescription>
          أضف طريقة دفع لإبقاء مساحة العمل نشطة.
        </AlertDescription>
        <AlertClose />
      </Alert>
    </div>
  )
}
KeyAction
TabMoves focus to links, actions and the close button inside the alert.
EnterSpaceActivates the focused close button. Once the alert has collapsed, focus moves to the next focusable element, or the previous one when nothing follows.
  • destructive and warning alerts get role="alert" and are announced right away. Every other variant gets role="status" and waits for a pause.
  • The close button is labelled “Dismiss”. Pass aria-label to translate it.
  • Focus is never lost when a focused alert closes.
  • With reduced motion on, alerts appear and disappear instantly.

Every part accepts the attributes of the element it renders. The variants are exported as alertVariants for styling other elements the same way.

PropTypeDefault
variantColors the icon. destructive and warning are announced assertively.
"default" | "destructive" | "success" | "info" | "warning""default"
appearance
"outline" | "soft""outline"
open
boolean–
defaultOpen
booleantrue
onOpenChangeCalled when the close button is pressed.
(open: boolean) => void–
render
ReactElement | (props, state) => ReactElement<div>
AttributeDescription
data-slot="alert"Target the alert in CSS.
data-variantThe current variant.
data-appearanceThe current appearance.
data-ending-stylePresent while the alert collapses after a dismiss.
PropTypeDefault
render
ReactElement | (props, state) => ReactElement<div>
AttributeDescription
data-slot="alert-title"Target titles in CSS.
PropTypeDefault
render
ReactElement | (props, state) => ReactElement<div>
AttributeDescription
data-slot="alert-description"Target descriptions in CSS. Plain links inside are underlined for you.
PropTypeDefault
render
ReactElement | (props, state) => ReactElement<div>
AttributeDescription
data-slot="alert-action"Target the action area in CSS.

A ghost <Button /> that closes the nearest alert. It accepts every Button prop and must be used inside <Alert />.

PropTypeDefault
aria-label
string"Dismiss"
children
ReactNode<IconX />
onClickCall event.preventDefault() to keep the alert open.
(event: MouseEvent) => void–
AttributeDescription
data-slot="alert-close"Target the close button in CSS.