Banner
A page-wide message under the header, one at a time, with a title, a line of text, one action and an optional dismiss. Server-rendered; only the dismiss button hydrates.
Import
import { Banner, BannerDismiss } from '@nizivo/react/banner';
// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/icon-button.css
import '@nizivo/react/css/banner.css';Static or interactive
Server, with one client leaf. Banner renders on the server and ships no JavaScript.
BannerDismiss, passed in dismiss, is the only part that hydrates; leave it out for a banner that
blocks work and must stay. The Banner does not hide itself: onDismiss updates your state, or is a
Server Action, and the page stops rendering it. How to choose.
In a client app (a Vite or React Router single-page app, or a 'use client' file) it is an ordinary React component: event handlers, refs and state work as usual. In a client app.
Banner or Alert
A Banner speaks to the whole page, under the header: a promotion, a plan about to renew. Show one at a time. An Alert sits next to the content it is about, inside a form or a card.
Tones
brand (the default) is Figma's Promo, on bg/brand-subtle, usually with media: a 72 px picture
before the text, such as a product render (<img alt="" width={72} height={72} />; it is
decorative). warning is on bg/warning-subtle, usually with an icon at 24 px. Figma names the
first tone "Promo"; code calls it brand, as Badge and Card do (a question for design, handoff Q25).
Layout
Figma's row: 16 / 24 px padding, 20 px between parts, the title (Heading/H4) and its line (Body/M)
in a column, the action and the dismiss after it, all centred. With less room the action wraps under
the text, and the dismiss stays at the row's end. Hooks: --nz-banner-bg, --nz-banner-gap,
--nz-banner-padding-block, --nz-banner-padding-inline, --nz-banner-radius,
--nz-banner-title-font, --nz-banner-description-font, --nz-banner-dismiss-fg.
The dismiss button
<BannerDismiss /> is a tertiary IconButton, 40 px (the action's height) with a 20 px close glyph
(Figma draws 18, which has no icon token), named "Dismiss" (labels={{ dismiss: '…' }} or
aria-label). With render, your element. A page that imports per-component stylesheets needs
css/icon-button.css too.
Screen readers
A banner has no live role by default: one that is on the page when it loads must not interrupt. For
one added after a user action, pass role="status", or say it with announce() from
@nizivo/react/toast.
Examples
Basic
Figma's Promo banner (tone brand): a render, a title and a line, one primary action. Server rendered and static: no dismiss, no client JS.
import type { JSX } from 'react';
import { Button } from '@nizivo/react/button';
import { Banner } from '@nizivo/react/banner';
/** A stand-in for a product render (use `<img alt="" width={72} height={72} />`). */
function Render(): JSX.Element {
return (
<svg viewBox="0 0 72 72" width="72" height="72" fill="none" stroke="currentColor">
<rect x="14" y="20" width="44" height="38" rx="6" />
<path d="M14 32h44M28 20v12M44 20v12" />
</svg>
);
}
export function BannerExample(): JSX.Element {
return (
<Banner
media={<Render />}
title="Open a second storefront in minutes"
action={<Button variant="primary">Add storefront</Button>}
>
Sell in EUR and GBP from the same catalogue.
</Banner>
);
}
Warning
A warning the user can dismiss. BannerDismiss is the only part that hydrates; the Banner does not hide itself: pass onDismiss from a client component, or a Server Action from a Server Component, and stop rendering it.
import type { JSX } from 'react';
import { Button } from '@nizivo/react/button';
import { Banner, BannerDismiss } from '@nizivo/react/banner';
/** A stand-in for `@nizivo/icons`' triangle-alert. */
function Warning(): JSX.Element {
return (
<svg viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor">
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
<path d="M12 9v4M12 17h.01" />
</svg>
);
}
export function BannerWarningExample({ onDismiss }: { onDismiss?: () => void }): JSX.Element {
return (
<Banner
tone="warning"
icon={<Warning />}
title="Your plan renews in 3 days"
action={<Button>Update card</Button>}
dismiss={<BannerDismiss {...(onDismiss && { onDismiss })} />}
>
Update the card on file to avoid interruption.
</Banner>
);
}
Props
Banner
A page-wide message under the header (Figma Banner 7275:15120): one at a time.
Server component Renders data-slot="banner" with the class nz-banner. Props: BannerProps. Source: src/banner/banner.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
action | ReactNode | – | One control after the text, such as |
dismiss | ReactNode | – |
|
icon | ReactNode | – | Decorative tone glyph at 24 px, such as a triangle for |
media | ReactNode | – | A 72 px picture before the text, such as Figma's product render on the brand banner: |
render | RenderProp<BannerState> | – | |
role | ComponentPropsWithRef<"div">["role"] | – | No live role by default: a banner present on page load must not interrupt. Pass |
title | ReactNode | – | One line, Heading/H4. The description is |
tone | BannerTone"brand""warning" | 'brand' |
- Also takes every attribute of
<div>excepttitle;roleis redeclared above. (Omit<ComponentPropsWithRef<"div">, "title">)
render takes an element, or, in client code, a function of the props and this state (BannerState):
| Name | Type | Default | Description |
|---|---|---|---|
tonerequired | BannerTone"brand""warning" | – |
BannerDismiss
With no render, an IconButton (tertiary, md: a 20 px glyph, Figma draws 18, in a 40 px target, the height of the Banner's action) with no tooltip, as Dialog.Close does.
Client component Renders data-slot="banner-dismiss" with the class nz-banner-dismiss. Props: BannerDismissProps. Source: src/banner/banner-dismiss.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | – | The close glyph, decorative. Defaults to the internal |
labels | Partial<BannerDismissLabels> | – | |
onDismiss | () => void | – | Called with no arguments, so a Server Component can pass a Server Action. The Banner does not hide itself: the caller stops rendering it. |
render | RenderProp<Record<string, never>> | – | Your element in place of the default IconButton; it must render a |
- Also takes every attribute of
<button>exceptchildren. (Omit<ComponentPropsWithRef<"button">, "children">)
render takes an element, or, in client code, a function of the props and no state.
Other exports
BannerDismissLabelstypeimport type { BannerDismissLabels } from '@nizivo/react/banner'Strings this part renders (§16.4).
Name Type Default Description dismissrequiredstring'Dismiss'The button's accessible name.
BannerDismissPropstypeimport type { BannerDismissProps } from '@nizivo/react/banner'The props of
BannerDismiss, listed above.BannerPropstypeimport type { BannerProps } from '@nizivo/react/banner'The props of
Banner, listed above.BannerStatetypeimport type { BannerState } from '@nizivo/react/banner'The render state of
Banner, listed above.BannerTonetypeimport type { BannerTone } from '@nizivo/react/banner'Figma "Tone":
brandis Figma's Promo (handoff Q25, D-bq).type BannerTone = "brand" | "warning"
Data attributes and slots
Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.
| Slot | Rendered by | Data attributes |
|---|---|---|
banner.nz-banner | Bannerroot (takes render) |
|
banner-action.nz-banner-action | Bannerinner element (CSS only) | none |
banner-content.nz-banner-content | Bannerinner element (CSS only) | none |
banner-description.nz-banner-description | Bannerinner element (CSS only) | none |
banner-dismiss.nz-banner-dismiss | BannerDismissroot (takes render); interactive, focus: self | none |
banner-icon.nz-banner-icon | Bannerinner element (CSS only) | none |
banner-media.nz-banner-media | Bannerinner element (CSS only) | none |
banner-title.nz-banner-title | Bannerinner element (CSS only) | none |
icon-button-icon.nz-icon-button-icon | BannerDismissinner element (CSS only) | none |
CSS hooks
Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.
| Hook | Default | Read as |
|---|---|---|
--nz-banner-bg |
|
|
--nz-banner-description-font | --nz-font-body-m |
|
--nz-banner-dismiss-fg | --nz-icon-secondary |
|
--nz-banner-gap | --nz-space-5 |
|
--nz-banner-icon-fg |
|
|
--nz-banner-padding-block | --nz-space-4 |
|
--nz-banner-padding-inline | --nz-space-6 |
|
--nz-banner-radius | --nz-radius-xl |
|
--nz-banner-title-font | --nz-font-heading-h4 |
|
Layers: nz.components. Classes: .nz-banner, .nz-banner-action, .nz-banner-content, .nz-banner-description, .nz-banner-dismiss, .nz-banner-icon, .nz-banner-media, .nz-banner-title, .nz-icon-button.
Tokens this stylesheet reads (18)
--nz-bg-brand-subtle, --nz-bg-warning-subtle, --nz-font-body-m, --nz-font-heading-h4, --nz-icon-brand, --nz-icon-secondary, --nz-icon-warning, --nz-radius-xl, --nz-size-icon-lg, --nz-space-0-5, --nz-space-2, --nz-space-4, --nz-space-5, --nz-space-6, --nz-text-primary, --nz-text-secondary, --nz-tracking-body-m, --nz-tracking-heading-h4
Accessibility
These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:
banner-dismiss: draws the focus outline itself on keyboard focus.
Keyboard
| Keys | Action |
|---|---|
| Tab | Reaches the action, then the dismiss button. The banner itself takes no focus. |
| Enter, Space | On the dismiss button, calls onDismiss; the banner leaves when your code stops rendering it. |
Source: Native <button> elements; no engine. Checked by the browser lane (test/browser/banner.spec.tsx) in Chromium, Firefox and WebKit.
Figma
Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.
- Canonical: Figma node
7275:15120 - Ghost masters: Figma node
756:913
Badge
A short status label with an optional decorative dot. The label carries the meaning, never the colour alone.
Breadcrumb
Where the page sits in the site, as a navigation landmark of links around an ordered list. The last level is the current page, as text with aria-current="page"; a router's link goes in through each item's render.