Skip to main content

Alert

Displays a callout for user attention.

Danger
This is a danger Alert.
Info
This is an info Alert.
Important
This is an important Alert.
Success
This is a success Alert.
Warning
This is a warning Alert.
Danger w/ custom icon
This is a danger Alert.
import { Alert } from "@ngrok/mantle/alert";import { ShrimpIcon } from "@phosphor-icons/react/Shrimp"; <Alert.Root intent="danger">	<Alert.Icon />	<Alert.Content>		<Alert.Title>Danger</Alert.Title>		<Alert.Description>This is a danger Alert.</Alert.Description>	</Alert.Content></Alert.Root><Alert.Root intent="info">	<Alert.Icon />	<Alert.Content>		<Alert.Title>Info</Alert.Title>		<Alert.DismissIconButton />		<Alert.Description>This is an info Alert.</Alert.Description>	</Alert.Content></Alert.Root><Alert.Root intent="important">	<Alert.Icon />	<Alert.Content>		<Alert.Title>Important</Alert.Title>		<Alert.Description>This is an important Alert.</Alert.Description>	</Alert.Content></Alert.Root><Alert.Root intent="success">	<Alert.Icon />	<Alert.Content>		<Alert.Title>Success</Alert.Title>		<Alert.Description>This is a success Alert.</Alert.Description>	</Alert.Content></Alert.Root><Alert.Root intent="warning">	<Alert.Icon />	<Alert.Content>		<Alert.Title>Warning</Alert.Title>		<Alert.Description>This is a warning Alert.</Alert.Description>	</Alert.Content></Alert.Root><Alert.Root intent="danger">	<Alert.Icon svg={<ShrimpIcon />} />	<Alert.Content>		<Alert.Title>Danger w/ custom icon</Alert.Title>		<Alert.Description>This is a danger Alert.</Alert.Description>	</Alert.Content></Alert.Root>

Composition

Compose the parts of an Alert together to build your own:

Alert.Root├── Alert.Icon├── Alert.Content    ├── Alert.Title    ├── Alert.Description    ├── Alert.DismissIconButton    └── Alert.ExpandButton

Polymorphism

Alert.Title and Alert.Description support asChild when the alert's surrounding document structure needs a different semantic element.

Maintenance window

Service may be briefly unavailable.

import { Alert } from "@ngrok/mantle/alert"; <Alert.Root intent="info">	<Alert.Icon />	<Alert.Content>		<Alert.Title asChild>			<h2>Maintenance window</h2>		</Alert.Title>		<Alert.Description asChild>			<p>Service may be briefly unavailable.</p>		</Alert.Description>	</Alert.Content></Alert.Root>;

Mix and match what you put inside the Alert component to create different Alert layouts.

Danger Will Robinson
Cupidatat ullamco commodo laborum consectetur ut mollit et nostrud amet elit ut Lorem culpa.
Danger Will Robinson
Cupidatat ullamco commodo laborum consectetur ut mollit et nostrud amet elit ut Lorem culpa.
Danger Will Robinson
Danger Will Robinson
import { Alert } from "@ngrok/mantle/alert"; // Danger Alert with icon and Dismiss Icon Button<Alert.Root intent="danger">	<Alert.Icon />	<Alert.Content>		<Alert.Title>Danger Will Robinson</Alert.Title>		<Alert.DismissIconButton />		<Alert.Description>This is a danger alert.</Alert.Description>	</Alert.Content></Alert.Root> // Danger Alert without icon<Alert.Root intent="danger">	<Alert.Content>		<Alert.Title>Danger Will Robinson</Alert.Title>		<Alert.Description>This is a danger alert.</Alert.Description>	</Alert.Content></Alert.Root> // Danger Alert with icon and no description<Alert.Root intent="danger">	<Alert.Icon />	<Alert.Content>		<Alert.Title>Danger Will Robinson</Alert.Title>	</Alert.Content></Alert.Root> // Danger Alert without icon or description, but including a Dismiss Icon Button<Alert.Root intent="danger">	<Alert.Content>		<Alert.Title>Danger Will Robinson</Alert.Title>		<Alert.DismissIconButton />	</Alert.Content></Alert.Root>

Banners

For banner-like alerts, use the appearance="banner" prop. This automatically removes the top, left, and right border, leaving only the bottom border.

This is an info Alert as a page banner

Laboris commodo Lorem anim consequat ut dolore proident.

import { Alert } from "@ngrok/mantle/alert"; <Alert.Root intent="info" appearance="banner">	<Alert.Icon />	<Alert.Content>		<Alert.Title>This is an info Alert as a page banner</Alert.Title>	</Alert.Content></Alert.Root>;

Dismissal

Alert.DismissIconButton removes nothing on its own. Your state does: stop rendering the alert in its onClick handler. When that unmount happens while the dismiss button holds focus, focus moves to the nearest tabbable element outside the alert: the next one in document order, or the previous one when nothing follows. Without that move, focus falls to <body> and the next Tab restarts at the top of the page. A screen reader user also gets no confirmation that the alert left. If your handler or a layout effect places focus first, the alert keeps that choice.

In the demo below, Tab to the dismiss button and press Enter. Focus lands on the button that follows the alert.

Your trial ends in 3 days
Add a payment method to keep your endpoints online.
import { Alert } from "@ngrok/mantle/alert";import { Button } from "@ngrok/mantle/button";import { useState } from "react"; export function TrialAlert() {	const [dismissed, setDismissed] = useState(false); 	return (		<div className="flex w-full flex-col gap-4">			{!dismissed && (				<Alert.Root intent="info">					<Alert.Icon />					<Alert.Content>						<Alert.Title>Your trial ends in 3 days</Alert.Title>						<Alert.Description>							Add a payment method to keep your endpoints online.						</Alert.Description>						<Alert.DismissIconButton onClick={() => setDismissed(true)} />					</Alert.Content>				</Alert.Root>			)}			<Button				type="button"				appearance="outlined"				intent="neutral"				onClick={() => setDismissed(false)}			>				Show the alert again			</Button>		</div>	);}

Expand button

Use Alert.ExpandButton for a compact control that reveals related alerts. It sits in the same trailing position as the dismiss button and reserves space for the count and caret, so alert copy remains contained on narrow viewports. It shows +N on mobile and +N more from the md (tablet) breakpoint.

Your workspace is approaching its monthly usage limit
import { Alert } from "@ngrok/mantle/alert"; const isExpanded = false;const toggle = () => {}; <Alert.Root intent="warning">	<Alert.Icon />	<Alert.Content>		<Alert.Title>Your workspace is approaching its monthly usage limit</Alert.Title>		<Alert.ExpandButton count={2} expanded={isExpanded} onClick={toggle} />	</Alert.Content></Alert.Root>;

Dismiss and expand

Alert.DismissIconButton and Alert.ExpandButton compose safely in the same alert. Both stay absolutely positioned at the trailing edge; when both are present, the dismiss button moves to the left of the expander and Alert.Content reserves room for the pair. This also handles an alert that first renders dismissable and later gains an expander as more alerts arrive.

Your workspace is approaching its monthly usage limit
import { Alert } from "@ngrok/mantle/alert"; const isExpanded = false;const dismiss = () => {};const toggle = () => {}; <Alert.Root intent="warning">	<Alert.Icon />	<Alert.Content>		<Alert.Title>Your workspace is approaching its monthly usage limit</Alert.Title>		<Alert.DismissIconButton onClick={dismiss} />		<Alert.ExpandButton count={2} expanded={isExpanded} onClick={toggle} />	</Alert.Content></Alert.Root>;

Trailing control colors

Alert.DismissIconButton and Alert.ExpandButton share the following CSS custom properties from Alert.Root. Each intent supplies the listed defaults; set them once on the root to theme both controls together.

VariableDefaultUsed for
--alert-control-colorvar(--color-<intent>-700)Control text and icon color.
--alert-control-hover-colorvar(--color-<intent>-800)Control text and icon color on hover.
--alert-control-hover-bgcolor-mix(in oklab, var(--color-<intent>-500) 10%, transparent)Control background on hover.
Usage limit approaching
import { $cssProperties } from "@ngrok/mantle/types";import { Alert } from "@ngrok/mantle/alert"; <Alert.Root	intent="warning"	style={$cssProperties({		"--alert-control-color": "var(--color-neutral-700)",		"--alert-control-hover-color": "var(--color-neutral-900)",		"--alert-control-hover-bg": "color-mix(in oklab, var(--color-neutral-500) 10%, transparent)",	})}>	<Alert.Icon />	<Alert.Content>		<Alert.Title>Usage limit approaching</Alert.Title>		<Alert.DismissIconButton />		<Alert.ExpandButton count={2} expanded={false} />	</Alert.Content></Alert.Root>;

Custom dismiss icon

Use the icon prop on Alert.DismissIconButton to render a custom icon in place of the default X.

Custom Dismiss Icon
This dismiss button uses a custom icon.
import { Alert } from "@ngrok/mantle/alert";import { ShrimpIcon } from "@phosphor-icons/react/Shrimp"; <Alert.Root intent="warning">	<Alert.Icon />	<Alert.Content>		<Alert.Title>Custom Dismiss Icon</Alert.Title>		<Alert.DismissIconButton icon={<ShrimpIcon />} />		<Alert.Description>This dismiss button uses a custom icon.</Alert.Description>	</Alert.Content></Alert.Root>;

Announcing a dynamic alert

Alert.Root renders a plain <div> with no ARIA role. A static alert that is on the page at load does not need one. When your code inserts an alert after an action, such as "Invalid email or password." after a submit, pass role="alert" so a screen reader announces it.

The role alone is not reliable. Assistive tech can miss a live region that mounts already populated. For an error the user must act on, also move focus to the alert: set tabIndex={-1} and focus it on mount. Focus also puts a sighted user's attention on what went wrong. For a less urgent message, pass role="status" on a region that is already mounted, and write the message into it.

The demo below rejects every sign-in, so the alert mounts after the action. The key remounts the alert on a repeated attempt, so it announces again.

import { Alert } from "@ngrok/mantle/alert";import { Button } from "@ngrok/mantle/button";import { Field } from "@ngrok/mantle/field";import { Input, PasswordInput } from "@ngrok/mantle/input";import type { PropsWithChildren } from "react";import { useEffect, useRef, useState } from "react"; function SignInError({ children }: PropsWithChildren) {	const alertRef = useRef<HTMLDivElement>(null);	useEffect(() => {		alertRef.current?.focus();	}, []); 	return (		<Alert.Root intent="danger" ref={alertRef} role="alert" tabIndex={-1}>			<Alert.Icon />			<Alert.Content>				<Alert.Description>{children}</Alert.Description>			</Alert.Content>		</Alert.Root>	);} export function SignInForm() {	const [attempts, setAttempts] = useState(0); 	return (		<form			className="flex w-full max-w-sm flex-col gap-4"			onSubmit={(event) => {				event.preventDefault();				setAttempts((count) => count + 1);			}}		>			{attempts > 0 && <SignInError key={attempts}>Invalid email or password.</SignInError>}			<Field.Item name="email">				<Field.Label>Email</Field.Label>				<Field.Control>					<Input type="email" autoComplete="username" />				</Field.Control>			</Field.Item>			<Field.Item name="password">				<Field.Label>Password</Field.Label>				<Field.Control>					<PasswordInput autoComplete="current-password" />				</Field.Control>			</Field.Item>			<Button type="submit" appearance="filled" intent="accent">				Sign in			</Button>		</form>	);}

API Reference

Alert.Root

Displays a callout for user attention. Root container for all Alert sub-components. It has no ARIA role of its own. When your code inserts the alert after an action, pass role="alert" and move focus to it; see Announcing a dynamic alert.

All props from div, plus:

PropTypeDefaultDescription
appearance?"default" | "banner""default"Controls the visual style. "default" keeps the standard rounded corners and borders. "banner" creates a banner-style alert with no rounded corners, sticky positioning, and no top, left, or right borders.
intent"danger" | "important" | "info" | "success" | "warning"The tone of the Alert. Sets the color, styling, and default icon to communicate the status it reports.

Trailing control CSS variables

Alert.Root defines --alert-control-color, --alert-control-hover-color, and --alert-control-hover-bg from its intent. Both Alert.DismissIconButton and Alert.ExpandButton inherit them. Override the variables on the root to theme both trailing controls together.

CSS VariableDefaultDescription
--alert-control-colorvar(--color-<intent>-700)Control text and icon color.
--alert-control-hover-colorvar(--color-<intent>-800)Control text and icon color on hover.
--alert-control-hover-bgcolor-mix(in oklab, var(--color-<intent>-500) 10%, transparent)Control background on hover.

Alert.Content

The container for the content slot of an Alert. Place Alert.Title, Alert.Description, Alert.DismissIconButton, and Alert.ExpandButton as direct children.

All props from div.

Alert.ExpandButton

Positions a compact expand/collapse control at the trailing edge of an Alert. Its count is displayed beside a caret that rotates when expanded is true. It shares the root-level trailing-control CSS variables with Alert.DismissIconButton.

All props from Button, plus:

PropTypeDefaultDescription
countnumber—The number of hidden related alerts, displayed with a leading +.
expandedboolean—Whether the related alerts are visible; rotates the caret when true.

Alert.Description

The optional description of an Alert. Renders as a div by default, but can be changed to any other element using the asChild prop.

All props from div, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the AlertDescription styling onto alternative element types or your own React components.

Alert.Icon

An optional icon that visually represents the intent of the Alert. Pass the svg prop to replace the default icon.

All props from svg, plus:

PropTypeDefaultDescription
svg?ReactNodeAn optional icon that renders in place of the default icon for the Alert intent.

Alert.Title

The title of an Alert. Renders as an h5 element by default; use asChild to render something else.

All props from h5, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the AlertTitle styling onto alternative element types or your own React components.

Alert.DismissIconButton

A dismiss icon button. Your onClick handler removes the alert. When that unmount happens while the button holds focus, focus moves to the nearest tabbable element outside the alert; see Dismissal. It shares the root-level trailing-control CSS variables with Alert.ExpandButton.

All props from IconButton, with the following overridden defaults:

PropTypeDefaultDescription
appearance?"filled" | "ghost" | "outlined""ghost"The visual style of the button.
icon?ReactNode<XIcon />An optional icon to render inside the dismiss button. Defaults to an X icon.
label?string"Dismiss Alert"The accessible label for the button, announced to screen readers.
size?"xs" | "sm" | "md""sm"The size of the button.
type?"button" | "submit" | "reset""button"The default behavior of the button.