stream/ui
Browse the library

Alert

A message box for info, success, warnings, and errors, with an icon, title, and optional actions.

stableAlert.tsx

Examples

Tones

New version available

v0.2 adds Dialog, Tooltip, and Accordion.

Changes saved

alert/tones.tsx
import { Alert } from "@streamuilib/react";

export default function AlertTones() {
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      <Alert tone="info" title="New version available">
        v0.2 adds Dialog, Tooltip, and Accordion.
      </Alert>
      <Alert tone="success" title="Changes saved" />
      <Alert tone="warning" title="Storage almost full">
        You’ve used 92% of your plan’s storage.
      </Alert>
      <Alert tone="danger" title="Payment failed">
        Your card was declined. Update it to keep your workspace.
      </Alert>
    </div>
  );
}

With actions

alert/with-action.tsx
import { Alert, Button } from "@streamuilib/react";

export default function AlertWithAction() {
  return (
    <Alert
      tone="danger"
      title="Deploy failed"
      className="w-full max-w-md"
      action={
        <>
          <Button size="sm">Retry deploy</Button>
          <Button size="sm" variant="secondary">
            View logs
          </Button>
        </>
      }
    >
      The build step exited with code 1 after 42 seconds.
    </Alert>
  );
}

Installation

bun add @streamuilib/react
usage.tsx
import { Alert } from "@streamuilib/react";

<Alert tone="success" title="Changes saved" />

API

PropTypeDefaultDescription
tone"info" | "success" | "warning" | "danger""info"Color, icon, and default role.
titleReactNode—Bold first line.
iconReactNode | null—Replaces the tone's icon; null hides it.
actionReactNode—Buttons or links under the message.
rolestring—Overrides the default "alert" or "status".
...propsComponentProps<"div">—Native div attributes.

Accessibility

  • Warnings and errors default to role="alert", which screen readers announce immediately; info and success use the politer role="status".
  • Live roles announce content that appears after load. For a message that's on the page from the start, pass role="note" (or another role) so it isn't announced as new.
  • The icon is decorative; the title and text carry the meaning, so color is never the only signal.

Source

packages/react/src/Alert.tsx
import type { ComponentProps, ReactNode } from "react";
import { CircleAlert, CircleCheck, Info, TriangleAlert } from "lucide-react";
import { cn } from "./cn";

export type AlertTone = "info" | "success" | "warning" | "danger";

const tones: Record<AlertTone, { surface: string; tile: string; icon: typeof Info }> = {
  info: { surface: "bg-blue-soft", tile: "bg-blue", icon: Info },
  success: { surface: "bg-lime-soft", tile: "bg-lime", icon: CircleCheck },
  warning: { surface: "bg-orange-soft", tile: "bg-orange", icon: TriangleAlert },
  danger: { surface: "bg-pink-soft", tile: "bg-pink", icon: CircleAlert },
};

export type AlertProps = Omit<ComponentProps<"div">, "title"> & {
  tone?: AlertTone;
  title?: ReactNode;
  /** Replaces the tone's default icon. Pass `null` for no icon. */
  icon?: ReactNode;
  /** Buttons or links shown under the message. */
  action?: ReactNode;
};

/**
 * A message box. Warnings and errors use role="alert" so screen readers
 * announce them right away; info and success use the politer role="status".
 */
export function Alert({ tone = "info", title, icon, action, className, children, role, ...props }: AlertProps) {
  const t = tones[tone];
  const urgent = tone === "warning" || tone === "danger";
  const glyph = icon === undefined ? <t.icon className="size-5" strokeWidth={2.25} /> : icon;

  return (
    <div
      role={role ?? (urgent ? "alert" : "status")}
      className={cn("flex gap-4 border-2 border-ink p-4 text-ink shadow-brutal-sm", t.surface, className)}
      {...props}
    >
      {glyph && (
        <span
          aria-hidden
          className={cn("grid size-9 shrink-0 place-items-center border-2 border-ink text-on-bright", t.tile)}
        >
          {glyph}
        </span>
      )}
      <div className="flex min-w-0 flex-1 flex-col gap-1 pt-0.5">
        {title && <p className="font-bold">{title}</p>}
        {children && <div className="text-sm leading-relaxed">{children}</div>}
        {action && <div className="mt-2 flex flex-wrap gap-3">{action}</div>}
      </div>
    </div>
  );
}