Banner
PortedA message in the page flow: information, success, a neutral note, a warning or an error. It is a live region: role="alert" for errors, role="status" for the rest. Pass role to change it. While title and description are empty, only the region is there, empty and hidden, so a message that comes later is announced, and <Banner variant="error" description={error} /> needs no condition.
import { type CSSProperties } from "react";
import { Banner, type BannerVariant } from "@etched/monet/banner";
const copy: Record<BannerVariant, [string, string]> = {
info: ["Firmware 2.4 is available", "Benches update at the end of the current run."],
success: ["Slack is connected", "Alerts for your chips now go to #bench-alerts."],
warning: ["Bench B-14 is due for calibration", "Results after 18:00 may drift."],
error: ["Export failed", "The report service did not respond. Try again in a minute."],
secondary: ["Department routing is off", "This mapping applies when routing is turned on."],
};
const stack: CSSProperties = { display: "grid", gap: "var(--et-space-3)" };
export function Preview() {
return (
<div style={stack}>
<Banner title={copy.info[0]} />
<Banner variant="success" title={copy.success[0]} />
<Banner variant="error" title={copy.error[0]} />
</div>
);
}Installation
import { Banner, BannerAction } from "@etched/monet/banner";Examples
Variants
import { type CSSProperties } from "react";
import { Banner, type BannerVariant } from "@etched/monet/banner";
const variants: BannerVariant[] = ["info", "success", "warning", "error", "secondary"];
const copy: Record<BannerVariant, [string, string]> = {
info: ["Firmware 2.4 is available", "Benches update at the end of the current run."],
success: ["Slack is connected", "Alerts for your chips now go to #bench-alerts."],
warning: ["Bench B-14 is due for calibration", "Results after 18:00 may drift."],
error: ["Export failed", "The report service did not respond. Try again in a minute."],
secondary: ["Department routing is off", "This mapping applies when routing is turned on."],
};
const stack: CSSProperties = { display: "grid", gap: "var(--et-space-3)" };
export function Variants() {
return (
<div style={stack}>
{variants.map((v) => (
<Banner key={v} variant={v} title={copy[v][0]} description={copy[v][1]} />
))}
</div>
);
}Message only, custom icon, rich text
import { FlaskIcon } from "@phosphor-icons/react";
import { type CSSProperties } from "react";
import { Banner } from "@etched/monet/banner";
const stack: CSSProperties = { display: "grid", gap: "var(--et-space-3)" };
export function MessageOnlyCustomIconRichText() {
return (
<div style={stack}>
<Banner variant="secondary" description="Slack delivery is paused. Your notification settings are saved." />
<Banner title="Profile fields are ready to edit." />
<Banner variant="secondary" icon={<FlaskIcon />} description="Experiments in this lab are visible to your team only." />
<Banner
description={
<>
<strong>Loading LinkedIn details…</strong> The candidate is saved. Details appear when the import ends.
</>
}
/>
</div>
);
}Actions and dismiss
BannerAction takes the banner's color: primary for the main action, secondary and ghost for the rest. onDismiss adds a close button.
import { ArrowsClockwiseIcon } from "@phosphor-icons/react";
import { useState, type CSSProperties } from "react";
import { Button } from "@etched/monet/button";
import { Banner, BannerAction } from "@etched/monet/banner";
const stack: CSSProperties = { display: "grid", gap: "var(--et-space-3)" };
export function ActionsAndDismiss() {
const [dismissed, setDismissed] = useState(false);
return (
<div style={stack}>
{dismissed ? (
<div className="row">
{/* Focus moves here when the banner is dismissed, so keyboard users keep their place. */}
<Button autoFocus onClick={() => setDismissed(false)}>
Show the firmware banner
</Button>
</div>
) : (
<Banner
title="Firmware 2.4 is available"
description="Benches update at the end of the current run."
action={<BannerAction>Update now</BannerAction>}
onDismiss={() => setDismissed(true)}
/>
)}
<Banner
variant="success"
title="Slack is connected"
description="Alerts for your chips now go to #bench-alerts."
action={<BannerAction variant="secondary">Send a test alert</BannerAction>}
/>
<Banner
variant="warning"
title="Bench B-15 is due for calibration"
action={
<>
<BannerAction variant="ghost">Remind me</BannerAction>
<BannerAction>Calibrate</BannerAction>
</>
}
/>
<Banner
variant="error"
title="Export failed"
description="The report service did not respond."
action={<BannerAction icon={<ArrowsClockwiseIcon />}>Retry export</BannerAction>}
/>
</div>
);
}Compact
import { type CSSProperties } from "react";
import { Banner, BannerAction, type BannerVariant } from "@etched/monet/banner";
const variants: BannerVariant[] = ["info", "success", "warning", "error", "secondary"];
const copy: Record<BannerVariant, [string, string]> = {
info: ["Firmware 2.4 is available", "Benches update at the end of the current run."],
success: ["Slack is connected", "Alerts for your chips now go to #bench-alerts."],
warning: ["Bench B-14 is due for calibration", "Results after 18:00 may drift."],
error: ["Export failed", "The report service did not respond. Try again in a minute."],
secondary: ["Department routing is off", "This mapping applies when routing is turned on."],
};
const stack: CSSProperties = { display: "grid", gap: "var(--et-space-3)" };
export function Compact() {
return (
<div style={stack}>
{variants.map((v) => (
<Banner key={v} size="sm" variant={v} description={copy[v][1]} />
))}
<Banner size="sm" variant="warning" title="Calibration due." description="Results after 18:00 may drift." />
<Banner size="sm" variant="error" description="report.pdf is larger than 25 MB." action={<BannerAction variant="secondary">Choose file</BannerAction>} />
</div>
);
}Accessibility
role="alert"forerror,role="status"for the other variants; passroleto change it. The icon is hidden; the close button is named bycloseLabel("Close").- Screen readers announce the text when it comes in: an alert at once, a status after what they are reading.
- With no
titleordescription, the empty region stays in the page, hidden, so a message set later is announced. - Keep the banner rendered and set
descriptionwhen the message comes, not the banner. InonDismiss, move focus if the close button had it.