Skip to content
Nizivo UI

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.

Server component with client leaf BannerDismiss

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.

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.

Open a second storefront in minutes
Sell in EUR and GBP from the same catalogue.
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.

Your plan renews in 3 days
Update the card on file to avoid interruption.
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

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.

NameTypeDefaultDescription
actionReactNode–

One control after the text, such as <Button variant="primary">Add storefront</Button>.

dismissReactNode–

<BannerDismiss onDismiss={…} />; leave it out for a banner that blocks work and stays.

iconReactNode–

Decorative tone glyph at 24 px, such as a triangle for warning; used when there is no media.

mediaReactNode–

A 72 px picture before the text, such as Figma's product render on the brand banner: <img src="…" alt="" width={72} height={72} />. Decorative; the text carries the message.

renderRenderProp<BannerState>–
roleComponentPropsWithRef<"div">["role"]–

No live role by default: a banner present on page load must not interrupt. Pass role="status" for one added after a user action.

titleReactNode–

One line, Heading/H4. The description is children.

toneBannerTone
"brand""warning"
'brand'
  • Also takes every attribute of <div> except title; role is redeclared above. (Omit<ComponentPropsWithRef<"div">, "title">)

render takes an element, or, in client code, a function of the props and this state (BannerState):

NameTypeDefaultDescription
tonerequiredBannerTone
"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.

NameTypeDefaultDescription
childrenReactNode–

The close glyph, decorative. Defaults to the internal close glyph (Lucide's x).

labelsPartial<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.

renderRenderProp<Record<string, never>>–

Your element in place of the default IconButton; it must render a <button>. A plain element keeps its own look. An IconButton, for example render={<IconButton icon={<X />} label="Dismiss" variant="tertiary" />} to add its tooltip, keeps its variant and size, and in a Banner takes the icon/secondary glyph colour, as a composed IconButton does in Dialog.Close.

  • Also takes every attribute of <button> except children. (Omit<ComponentPropsWithRef<"button">, "children">)

render takes an element, or, in client code, a function of the props and no state.

Other exports

BannerDismissLabels type

import type { BannerDismissLabels } from '@nizivo/react/banner'

Strings this part renders (§16.4).

NameTypeDefaultDescription
dismissrequiredstring'Dismiss'

The button's accessible name.

BannerDismissProps type

import type { BannerDismissProps } from '@nizivo/react/banner'

The props of BannerDismiss, listed above.

BannerProps type

import type { BannerProps } from '@nizivo/react/banner'

The props of Banner, listed above.

BannerState type

import type { BannerState } from '@nizivo/react/banner'

The render state of Banner, listed above.

BannerTone type

import type { BannerTone } from '@nizivo/react/banner'

Figma "Tone": brand is 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.

SlotRendered byData attributes
banner
.nz-banner
Banner
root (takes render)
  • data-tone: "brand""warning" ← tone
    The tone prop; values in each component manifest
banner-action
.nz-banner-action
Banner
inner element (CSS only)
none
banner-content
.nz-banner-content
Banner
inner element (CSS only)
none
banner-description
.nz-banner-description
Banner
inner element (CSS only)
none
banner-dismiss
.nz-banner-dismiss
BannerDismiss
root (takes render); interactive, focus: self
none
banner-icon
.nz-banner-icon
Banner
inner element (CSS only)
none
banner-media
.nz-banner-media
Banner
inner element (CSS only)
none
banner-title
.nz-banner-title
Banner
inner element (CSS only)
none
icon-button-icon
.nz-icon-button-icon
BannerDismiss
inner 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.

HookDefaultRead as
--nz-banner-bg
  • default: var(--nz-bg-brand-subtle)
  • data-tone="warning": var(--nz-bg-warning-subtle)
  • background on .nz-banner
--nz-banner-description-font--nz-font-body-m
  • font on .nz-banner-description
--nz-banner-dismiss-fg--nz-icon-secondary
  • --_nz-icon-button-fg on .nz-banner > .nz-icon-button.nz-banner-dismiss
--nz-banner-gap--nz-space-5
  • gap on .nz-banner
--nz-banner-icon-fg
  • default: var(--nz-icon-brand)
  • data-tone="warning": var(--nz-icon-warning)
  • color on .nz-banner-icon
--nz-banner-padding-block--nz-space-4
  • padding-block on .nz-banner
--nz-banner-padding-inline--nz-space-6
  • padding-inline on .nz-banner
--nz-banner-radius--nz-radius-xl
  • border-radius on .nz-banner
--nz-banner-title-font--nz-font-heading-h4
  • font on .nz-banner-title

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

KeysAction
TabReaches the action, then the dismiss button. The banner itself takes no focus.
Enter, SpaceOn 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.

On this page