Component
Alert
Alerts surface system-level feedback tied to the current page or action — form results, status messages, and contextual notices. Six semantic variants, each wired to the --components-alerts-* tokens, render across both Desktop/Tablet and Mobile Application layouts.
Playground
Variant
Title
Supporting text
Description
Action
Action label
Leading icon
Close icon
Preview
We’ve just released a new feature
Exciting news! We've just launched 'Starlight', a cutting-edge feature designed to elevate your experience. Dive into enhanced collaboration, streamlined workflows, and personalized insights. Explore the future of productivity with 'Starlight.'
Learn More
JSX
<Alert
variant="primary"
title="We’ve just released a new feature"
content="Exciting news! We've just launched 'Starlight', a cutting-edge feature designed to elevate your experience. Dive into enhanced collaboration, streamlined workflows, and personalized insights. Explore the future of productivity with 'Starlight.'"
showLeftIcon
showRightIcon
showActionButton
actionLabel="View Changes"
/>Props
The full AlertProps API. Props marked Required have no default.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'primary' | 'error' | 'warning' | 'success' | 'information' | 'neutral' | 'primary' | Alert type — controls the color tokens and the default leading icon. |
titleRequired | string | — | Heading text. Truncates to a single line. Never render content-only. |
content | string | — | Supporting context text below the title. Truncates to 2 lines. |
platform | 'desktop' | 'mobile' | 'desktop' | Layout context. Both platforms render a text action row; 'mobile' uses tighter padding and smaller icons. |
showLeftIcon | boolean | true | Show the leading status icon (decorative). |
showRightIcon | boolean | true | Show the trailing dismiss (close) control. |
showActionButton | boolean | true | Show the action area below the text. |
actionLabel | string | 'View Changes' | Trailing text CTA with a forward arrow. Desktop pairs it with a fixed "Learn More" label; mobile pairs it with dismissLabel. |
dismissLabel | string | 'Dismiss' | Mobile action — leading dismiss text label. Ignored on desktop. |
onAction | () => void | — | Called when the mobile action CTA (actionLabel) is activated. |
onDismiss | () => void | — | Called when the dismiss control (right icon, or mobile dismiss label) is activated. |
className | string | — | Layout-only classes (margin, width, positioning). |
Usage guidelines
When to use
- Surface contextual feedback — confirmations, warnings, or errors — tied to the current page or action.
- Pair a leading icon with the matching variant colour so the message type is scannable at a glance.
- Include an action when the user needs to respond to or act on the message.
When not to use
- Don't use for critical errors that block the whole flow — use a full-page state instead.
- Don't stack multiple alerts; consolidate into one, or use a toast queue for transient messages.
- Don't replace inline form-field validation; show those errors directly beneath the field.
nexus/components/alert.tsx
import type { ComponentType, SVGProps } from "react";
import {
InfoOutlineIcon,
ErrorOutlineIcon,
WarningOutlineIcon,
CheckCircleOutlineIcon,
CloseOutlineIcon,
ArrowForwardOutlineIcon,
} from "nexus/icons";
type IconType = ComponentType<SVGProps<SVGSVGElement>>;
export type AlertVariant =
| "primary"
| "error"
| "warning"
| "success"
| "information"
| "neutral";
export type AlertPlatform = "desktop" | "mobile";
export interface AlertProps {
/** Alert type — controls color tokens and the default left icon. */
variant?: AlertVariant;
/** Heading text. Truncates to 1 line. Required — never render content-only. */
title: string;
/** Supporting context text. Truncates to 2 lines. */
content?: string;
/** Layout context. Both `desktop` (default) and `mobile` render a text action row. */
platform?: AlertPlatform;
/** Show the leading status icon (decorative). */
showLeftIcon?: boolean;
/** Show the trailing dismiss (close) control. */
showRightIcon?: boolean;
/** Show the action area below the text. */
showActionButton?: boolean;
/** Trailing text CTA (e.g. "View Changes"), with a forward arrow. Desktop pairs it with a fixed "Learn More" label; mobile pairs it with `dismissLabel`. */
actionLabel?: string;
/** Mobile action — leading dismiss text label. Defaults to "Dismiss". Ignored on desktop. */
dismissLabel?: string;
/** Called when the action CTA (`actionLabel`) is activated. */
onAction?: () => void;
/** Called when the dismiss control (right icon, or mobile dismiss label) is activated. */
onDismiss?: () => void;
/** Layout-only classes (margin, width, positioning). */
className?: string;
}
interface VariantStyle {
/** Background fill + border color. */
surface: string;
/** Title text color. */
titleText: string;
/** Content text color. */
contentText: string;
/** Path fill for the left / right icons. */
iconFill: string;
/** Text color for the mobile action label. */
actionText: string;
/** Path fill for the mobile action arrow. */
actionFill: string;
/** Default leading icon for the variant. */
LeftIcon: IconType;
}
// Static per-variant class strings — Tailwind only emits utilities it can see
// as literals, so these must never be assembled at runtime. Tokens live in
// nexus/styles/tokens.css (note the source "nuetral" spelling for neutral).
const VARIANTS: Record<AlertVariant, VariantStyle> = {
primary: {
surface:
"bg-[var(--components-alerts-color-primary-background-subtle)] border-[color:var(--components-alerts-color-primary-border-default)]",
titleText: "text-[var(--components-alerts-color-primary-text-primary)]",
contentText: "text-[var(--components-alerts-color-primary-text-primary)]",
iconFill: "[&_path]:fill-[var(--components-alerts-color-primary-icon-primary)]",
actionText: "text-[var(--components-alerts-color-primary-text-primary)]",
actionFill: "[&_path]:fill-[var(--components-alerts-color-primary-text-primary)]",
LeftIcon: InfoOutlineIcon,
},
error: {
surface:
"bg-[var(--components-alerts-color-error-background-subtle)] border-[color:var(--components-alerts-color-error-border-default)]",
titleText: "text-[var(--components-alerts-color-error-text-primary)]",
contentText: "text-[var(--components-alerts-color-error-text-primary)]",
iconFill: "[&_path]:fill-[var(--components-alerts-color-error-icon-primary)]",
actionText: "text-[var(--components-alerts-color-error-text-primary)]",
actionFill: "[&_path]:fill-[var(--components-alerts-color-error-text-primary)]",
LeftIcon: ErrorOutlineIcon,
},
warning: {
surface:
"bg-[var(--components-alerts-color-warning-background-subtle)] border-[color:var(--components-alerts-color-warning-border-default)]",
titleText: "text-[var(--components-alerts-color-warning-text-primary)]",
contentText: "text-[var(--components-alerts-color-warning-text-primary)]",
iconFill: "[&_path]:fill-[var(--components-alerts-color-warning-icon-primary)]",
actionText: "text-[var(--components-alerts-color-warning-text-primary)]",
actionFill: "[&_path]:fill-[var(--components-alerts-color-warning-text-primary)]",
LeftIcon: WarningOutlineIcon,
},
success: {
surface:
"bg-[var(--components-alerts-color-success-background-subtle)] border-[color:var(--components-alerts-color-success-border-default)]",
titleText: "text-[var(--components-alerts-color-success-text-primary)]",
contentText: "text-[var(--components-alerts-color-success-text-primary)]",
iconFill: "[&_path]:fill-[var(--components-alerts-color-success-icon-primary)]",
actionText: "text-[var(--components-alerts-color-success-text-primary)]",
actionFill: "[&_path]:fill-[var(--components-alerts-color-success-text-primary)]",
LeftIcon: CheckCircleOutlineIcon,
},
information: {
surface:
"bg-[var(--components-alerts-color-information-background-subtle)] border-[color:var(--components-alerts-color-information-border-default)]",
titleText: "text-[var(--components-alerts-color-information-text-primary)]",
contentText: "text-[var(--components-alerts-color-information-text-primary)]",
iconFill: "[&_path]:fill-[var(--components-alerts-color-information-icon-primary)]",
actionText: "text-[var(--components-alerts-color-information-text-primary)]",
actionFill: "[&_path]:fill-[var(--components-alerts-color-information-text-primary)]",
LeftIcon: InfoOutlineIcon,
},
neutral: {
surface:
"bg-[var(--components-alerts-color-nuetral-background-subtle)] border-[color:var(--components-alerts-color-nuetral-border-default)]",
titleText: "text-[var(--components-alerts-color-nuetral-text-primary)]",
contentText: "text-[var(--components-alerts-color-nuetral-text-muted)]",
iconFill: "[&_path]:fill-[var(--components-alerts-color-nuetral-icon-fg)]",
actionText: "text-[var(--components-alerts-color-nuetral-text-muted)]",
actionFill: "[&_path]:fill-[var(--components-alerts-color-nuetral-text-muted)]",
LeftIcon: InfoOutlineIcon,
},
};
// error / warning interrupt assertively; success / information announce
// politely; primary / neutral are non-live contextual notes (§6.1 a11y).
const ROLE: Record<AlertVariant, "alert" | "status" | "note"> = {
error: "alert",
warning: "alert",
success: "status",
information: "status",
primary: "note",
neutral: "note",
};
const cx = (...classes: (string | false | undefined)[]) =>
classes.filter(Boolean).join(" ");
/**
* Nexus Alert — system-level feedback message. Single component covering both
* Desktop/Tablet and Mobile Application layouts via the `platform` prop, per
* the design spec (instruction/nexus-design-system.md §6.1).
*/
export const Alert = ({
variant = "primary",
title,
content,
platform = "desktop",
showLeftIcon = true,
showRightIcon = true,
showActionButton = true,
actionLabel = "View Changes",
dismissLabel = "Dismiss",
onAction,
onDismiss,
className,
}: AlertProps) => {
const style = VARIANTS[variant];
const isMobile = platform === "mobile";
const iconSize = isMobile ? 18 : 24;
const arrowSize = isMobile ? 18 : 20;
const LeftIcon = style.LeftIcon;
const hasAction = showActionButton;
return (
<div
role={ROLE[variant]}
className={cx(
"flex items-start rounded-4 border-[length:var(--stroke-1)] border-solid",
style.surface,
isMobile
? "w-[328px] min-w-[328px] max-w-[396px] gap-8 p-16"
: "w-[1152px] min-w-[640px] max-w-[1600px] gap-12 px-16 py-20 shadow-sm",
className,
)}
>
{showLeftIcon && (
<LeftIcon
width={iconSize}
height={iconSize}
className={cx("shrink-0", style.iconFill)}
/>
)}
<div className="flex min-w-0 flex-1 flex-col gap-12">
<div className={cx("flex flex-col", isMobile ? "gap-4" : "gap-8")}>
<p
className={cx(
"w-full truncate",
isMobile ? "typography-mobile-b1-semibold-14" : "typography-b1-semibold-16",
style.titleText,
)}
>
{title}
</p>
{content && (
<p
className={cx(
"w-full line-clamp-2",
isMobile ? "typography-mobile-b1-regular-14" : "typography-b1-regular-16",
style.contentText,
)}
>
{content}
</p>
)}
</div>
{hasAction && (
<div className="flex items-center gap-12">
{isMobile ? (
// Mobile renders actions as plain text labels — never Buttons (§6.1):
// a leading "Dismiss", then an actionLabel CTA with a forward arrow
// sized to match the label's line height.
<>
<button
type="button"
onClick={onDismiss}
className={cx(
"whitespace-nowrap typography-mobile-b1-medium-14",
style.actionText,
)}
>
{dismissLabel}
</button>
{actionLabel && (
<button
type="button"
onClick={onAction}
className={cx(
"inline-flex items-center gap-4 whitespace-nowrap typography-mobile-b1-medium-14",
style.actionText,
)}
>
{actionLabel}
<ArrowForwardOutlineIcon
width={arrowSize}
height={arrowSize}
className={cx("shrink-0", style.actionFill)}
/>
</button>
)}
</>
) : (
// Desktop renders actions as plain text too — a fixed "Learn More"
// label, then the actionLabel CTA with a forward arrow.
<>
<p
className={cx(
"whitespace-nowrap typography-b2-medium-14",
style.actionText,
)}
>
Learn More
</p>
{actionLabel && (
<button
type="button"
onClick={onAction}
className={cx(
"inline-flex items-center gap-8 whitespace-nowrap typography-b2-medium-14",
style.actionText,
)}
>
{actionLabel}
<ArrowForwardOutlineIcon
width={arrowSize}
height={arrowSize}
className={cx("shrink-0", style.actionFill)}
/>
</button>
)}
</>
)}
</div>
)}
</div>
{showRightIcon && (
<button
type="button"
onClick={onDismiss}
aria-label="Dismiss alert"
className="shrink-0"
>
<CloseOutlineIcon width={iconSize} height={iconSize} className={style.iconFill} />
</button>
)}
</div>
);
};