Alert

A callout that draws attention to an important message.

Heads up
Your trial ends in 3 days.

Installation

Terminal
pnpm dlx @dowel-ui/cli add alert

npm packages installed: class-variance-authority.

Accessibility

Not a live region by default. Pass live="polite" for alerts that appear in response to a user action, or live="assertive" for errors that must interrupt. Variant colour is never the only signal — the title carries the meaning.

Props

Alert

PropTypeDefault
liveAlertLive"off"
variant"default" | "destructive" | "success" | "warning" | "info""default"

Plus every attribute of <div>.

AlertTitle

Plus every attribute of <div>.

AlertDescription

Plus every attribute of <div>.

Quality

7/7 checks, measured from the source and its tests

  • Testedpasses
  • axe assertionpasses
  • Keyboard testeddoes not apply
  • Storybook examplespasses
  • Accessibility documentedpasses
  • Semantic tokens onlypasses
  • Motion from tokensdoes not apply
  • className mergedpasses
  • Visible focusdoes not apply
  • No fixed widthspasses

Used in

Whole screens assembled from this component. Installing one brings this and everything else it needs with it.

Source

This is exactly what dowel add alert writes into your project, with imports rewritten to your own path alias.

ui/alert.tsx
import { cva, type VariantProps } from "class-variance-authority";
import type { ComponentPropsWithRef } from "react";

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

const alertVariants = cva(
  cn(
    "relative grid w-full grid-cols-[0_1fr] items-start gap-y-1 rounded-lg border px-4 py-3 text-sm",
    "has-[>svg]:grid-cols-[calc(var(--size-icon-md))_1fr] has-[>svg]:gap-x-3",
    "[&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
  ),
  {
    variants: {
      variant: {
        default: "border-border bg-card text-card-foreground",
        destructive:
          "border-destructive/30 bg-destructive/8 text-foreground [&>svg]:text-destructive",
        success: "border-success/30 bg-success/8 text-foreground [&>svg]:text-success",
        warning: "border-warning/35 bg-warning/10 text-foreground [&>svg]:text-warning",
        info: "border-info/30 bg-info/8 text-foreground [&>svg]:text-info",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  },
);

/**
 * How urgently the alert should interrupt a screen reader.
 *
 * - `off` (default): a static notice already present when the page rendered.
 * - `polite`: appeared in response to something the user did; announce at the
 *   next pause.
 * - `assertive`: an error that must interrupt whatever is being read.
 */
export type AlertLive = "off" | "polite" | "assertive";

export interface AlertProps
  extends ComponentPropsWithRef<"div">, VariantProps<typeof alertVariants> {
  live?: AlertLive;
}

/**
 * A callout that draws attention to a message.
 *
 * `live` defaults to `off` rather than always rendering role="alert". A live
 * region that exists on first paint announces itself for no reason and trains
 * users to ignore it; opt in when the alert actually appears in response to
 * something.
 */
export function Alert({ className, variant, live = "off", ...props }: AlertProps) {
  const role = live === "assertive" ? "alert" : live === "polite" ? "status" : undefined;

  return (
    <div
      data-slot="alert"
      role={role}
      aria-live={live === "off" ? undefined : live}
      className={cn(alertVariants({ variant }), className)}
      {...props}
    />
  );
}

export function AlertTitle({ className, ...props }: ComponentPropsWithRef<"div">) {
  return (
    <div
      data-slot="alert-title"
      className={cn("col-start-2 font-medium tracking-tight", className)}
      {...props}
    />
  );
}

export function AlertDescription({ className, ...props }: ComponentPropsWithRef<"div">) {
  return (
    <div
      data-slot="alert-description"
      className={cn(
        "col-start-2 text-sm text-muted-foreground [&_p]:leading-relaxed",
        className,
      )}
      {...props}
    />
  );
}

export { alertVariants };