Alert

Displays a callout for user attention.

import * as stylex from "@stylexjs/stylex"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { VStack } from "@/components/ui/stack"
import { CircleCheck, Info } from "lucide-react"

const styles = stylex.create({
  stack: { width: "100%", maxWidth: "28rem" },
})

export default function AlertDemo() {
  return (
    <VStack gap={4} xstyle={styles.stack}>
      <Alert>
        <CircleCheck />
        <AlertTitle>Payment successful</AlertTitle>
        <AlertDescription>
          Your payment of $29.99 has been processed. A receipt has been sent to your email address.
        </AlertDescription>
      </Alert>
      <Alert>
        <Info />
        <AlertTitle>New feature available</AlertTitle>
        <AlertDescription>
          We&apos;ve added dark mode support. You can enable it in your account settings.
        </AlertDescription>
      </Alert>
    </VStack>
  )
}

Installation

Install the following dependencies:

pnpm add @base-ui/react @stylexjs/stylex clsx

Set up the token layer and the StyleX compiler, if you have not already. See Installation. The alert also needs interop.css, which places the icon and sizes it.

Copy and paste the following code into your project.

components/ui/alert.tsx
"use client"

import { useRender } from "@base-ui/react/use-render"
import * as stylex from "@stylexjs/stylex"
import type { StyleXStyles } from "@stylexjs/stylex"
import { clsx } from "clsx"

import { breakpoints } from "@/theme/breakpoints.stylex"
import { color, leading, radius, shadow, space, text } from "@/theme/tokens.stylex"

const styles = stylex.create({
  root: {
    display: "grid",
    gridTemplateColumns: "auto 1fr auto",
    rowGap: space.s0_5,
    width: "100%",
    paddingInline: space.s2_5,
    paddingBlock: space.s2,
    borderRadius: radius.lg,
    borderWidth: 1,
    borderStyle: "solid",
    backgroundColor: color.card,
    boxShadow: shadow.xs,
    textAlign: "start",
    fontSize: text.sm,
    lineHeight: leading.sm,
    "--carbon-icon-size": space.s4,
  },
  title: { gridColumn: "2", fontWeight: 500 },
  description: {
    gridColumn: "2",
    color: "var(--carbon-alert-description)",
    textWrap: { default: "balance", [breakpoints.md]: "pretty" },
  },
  action: {
    gridColumn: "3",
    gridRowStart: "1",
    gridRowEnd: "span 2",
    alignSelf: "start",
    marginInlineStart: space.s2,
  },
})

const variants = stylex.create({
  default: {
    color: color.cardForeground,
    "--carbon-alert-description": color.mutedForeground,
  },
  destructive: {
    color: color.destructive,
    "--carbon-alert-description": `color-mix(in oklab, ${color.destructive} 90%, transparent)`,
  },
})

const alertStyles = { base: styles.root, variants }

type AlertVariant = keyof typeof variants

type Overrides = { xstyle?: StyleXStyles; className?: string }

type AlertProps = useRender.ComponentProps<"div"> & Overrides & { variant?: AlertVariant }

function Alert({
  render,
  ref,
  style,
  variant = "default",
  xstyle,
  className,
  ...props
}: AlertProps) {
  const sx = stylex.props(styles.root, variants[variant] ?? variants.default, xstyle)
  return useRender({
    defaultTagName: "div",
    render,
    ref,
    props: {
      "data-slot": "alert",
      "data-variant": variant,
      role: "alert",
      ...sx,
      ...props,
      className: clsx("carbon-icons", sx.className, className),
      style: { ...sx.style, ...style },
    },
  })
}

function AlertTitle({
  render,
  ref,
  style,
  xstyle,
  className,
  ...props
}: useRender.ComponentProps<"div"> & Overrides) {
  const sx = stylex.props(styles.title, xstyle)
  return useRender({
    defaultTagName: "div",
    render,
    ref,
    props: {
      "data-slot": "alert-title",
      ...sx,
      ...props,
      className: clsx(sx.className, className),
      style: { ...sx.style, ...style },
    },
  })
}

function AlertDescription({
  render,
  ref,
  style,
  xstyle,
  className,
  ...props
}: useRender.ComponentProps<"div"> & Overrides) {
  const sx = stylex.props(styles.description, xstyle)
  return useRender({
    defaultTagName: "div",
    render,
    ref,
    props: {
      "data-slot": "alert-description",
      ...sx,
      ...props,
      className: clsx(sx.className, className),
      style: { ...sx.style, ...style },
    },
  })
}

function AlertAction({
  render,
  ref,
  style,
  xstyle,
  className,
  ...props
}: useRender.ComponentProps<"div"> & Overrides) {
  const sx = stylex.props(styles.action, xstyle)
  return useRender({
    defaultTagName: "div",
    render,
    ref,
    props: {
      "data-slot": "alert-action",
      ...sx,
      ...props,
      className: clsx(sx.className, className),
      style: { ...sx.style, ...style },
    },
  })
}

export { Alert, AlertAction, AlertDescription, AlertTitle, alertStyles, type AlertVariant }

Update the import paths to match your project setup.

Usage

import { Alert, AlertAction, AlertDescription, AlertTitle } from "@/components/ui/alert"
<Alert>
  <InfoIcon />
  <AlertTitle>Heads up!</AlertTitle>
  <AlertDescription>You can add components to your app using the cli.</AlertDescription>
  <AlertAction>
    <Button variant="outline">Enable</Button>
  </AlertAction>
</Alert>

Composition

Alert
├── Icon
├── AlertTitle
├── AlertDescription
└── AlertAction

The icon is any svg passed as a direct child. The alert is a three-column grid, and interop.css places the icon in the first column and the action in the last. Both columns collapse to nothing when empty.

Basic

A basic alert with an icon, title and description.

import * as stylex from "@stylexjs/stylex"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { CircleCheck } from "lucide-react"

const styles = stylex.create({
  alert: { maxWidth: "28rem" },
})

export default function AlertBasic() {
  return (
    <Alert xstyle={styles.alert}>
      <CircleCheck />
      <AlertTitle>Account updated successfully</AlertTitle>
      <AlertDescription>
        Your profile information has been saved. Changes will be reflected immediately.
      </AlertDescription>
    </Alert>
  )
}

Destructive

Use variant="destructive" to create a destructive alert.

import * as stylex from "@stylexjs/stylex"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { CircleAlert } from "lucide-react"

const styles = stylex.create({
  alert: { maxWidth: "28rem" },
})

export default function AlertDestructive() {
  return (
    <Alert variant="destructive" xstyle={styles.alert}>
      <CircleAlert />
      <AlertTitle>Payment failed</AlertTitle>
      <AlertDescription>
        Your payment could not be processed. Please check your payment method and try again.
      </AlertDescription>
    </Alert>
  )
}

Action

Use AlertAction to add a button or other action element to the alert.

import * as stylex from "@stylexjs/stylex"
import { Alert, AlertAction, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Button } from "@/components/ui/button"

const styles = stylex.create({
  alert: { maxWidth: "28rem" },
})

export default function AlertActionDemo() {
  return (
    <Alert xstyle={styles.alert}>
      <AlertTitle>Dark mode is now available</AlertTitle>
      <AlertDescription>Enable it under your profile settings to get started.</AlertDescription>
      <AlertAction>
        <Button size="xs">Enable</Button>
      </AlertAction>
    </Alert>
  )
}

Custom colors

Pass xstyle to change the border, background and text colors. Set --carbon-alert-description in the same block to recolor the description, which otherwise reads the variant's muted color.

import * as stylex from "@stylexjs/stylex"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { color } from "@/theme/tokens.stylex"
import { TriangleAlert } from "lucide-react"

const styles = stylex.create({
  alert: {
    maxWidth: "28rem",
    borderColor: color.calloutWarnBorder,
    backgroundColor: color.calloutWarnBg,
    color: color.calloutWarnAccent,
    "--carbon-alert-description": color.calloutWarnAccent,
  },
})

export default function AlertColors() {
  return (
    <Alert xstyle={styles.alert}>
      <TriangleAlert />
      <AlertTitle>Your subscription will expire in 3 days.</AlertTitle>
      <AlertDescription>
        Renew now to avoid service interruption or upgrade to a paid plan to continue using the
        service.
      </AlertDescription>
    </Alert>
  )
}

API reference

Pass render to replace a part's element or compose it with another component.

Alert

Renders a div with role="alert". Every part also takes xstyle and className.

PropTypeDefault
variant"default" | "destructive""default"

alertStyles

The style map behind the component, exported so you can apply the same styles to any element.

KeyDescription
baseThe grid, padding, border and type
variantsOne entry per variant, keyed by its name

Data attributes

AttributeOnEffect
data-slot="alert"the rootWhere interop.css keys the icon's grid slot
data-variantthe rootReflects the variant prop
data-slot="alert-title"the titleLinks inside are underlined by interop.css
data-slot="alert-description"the descriptionLinks inside are underlined by interop.css
data-slot="alert-action"the actionTargets the action from a parent

CSS variables

VariableOnEffect
--carbon-alert-descriptionthe rootThe description's color. Set by the variant, read by the part