NimUI
ComponentsFeedback

Banner

Full-bleed system notice bar for page-level announcements

Banner is the app-frame notice bar: maintenance windows, degraded integrations, trial expiry, read-only mode. It spans the full width of the shell — sitting above or inside AdminShell chrome — so a single message reaches every screen without stealing space from the working area.

It is deliberately not an Alert. Alert is inline and content-scoped: it explains the form you are looking at, keeps a card radius, and stays where you put it. Banner is chrome — denser vertically, square-cornered with a bottom border only, optionally sticky, and dismissible by the operator.

Import

import { Banner } from '@nim-ui/components';

Playground

Change the tone and watch the ARIA role follow: warning and error render role="alert" so screen readers interrupt; everything else is a polite role="status".

Editable
<div className="w-full overflow-hidden rounded-md border border-neutral-200 dark:border-neutral-800">
<Banner tone="warning" title="Scheduled maintenance">
  Order sync pauses Sunday 02:00–04:00 UTC.
</Banner>
<Banner
  tone="info"
  title="Trial ends in 5 days"
  dismissible
  action={<Button size="sm">Upgrade plan</Button>}
>
  Add billing details to keep fulfillment running.
</Banner>
<Banner tone="success" title="Warehouse sync restored" dismissible />
<Banner align="center" icon={null}>
  Read-only mode — you are viewing a historical snapshot.
</Banner>
</div>

Tones

neutral uses the standard surface treatment for routine announcements. The four status tones are soft tonal, matching Badge and StatusPill so a banner reads as the same severity language used elsewhere in the app.

Tones

Release 2026.7 is live

Changelog is available in Settings.

New export limits

CSV exports now cap at 50k rows.

Warehouse sync restored

All queues are draining normally.
Code
<Banner title="Release 2026.7 is live">Changelog is available in Settings.</Banner>
<Banner tone="info" title="New export limits">CSV exports now cap at 50k rows.</Banner>
<Banner tone="success" title="Warehouse sync restored">All queues are draining normally.</Banner>
<Banner tone="warning" title="Scheduled maintenance">Order sync pauses Sunday 02:00–04:00 UTC.</Banner>
<Banner tone="error" title="Payment provider degraded">Captures are queued and will retry automatically.</Banner>

Action and dismissal

Give the operator one way out: an action that resolves the notice, a dismiss button that hides it, or both. Dismissal is uncontrolled — the banner calls onDismiss and removes itself from the page, so persisting the choice is your job.

Action and Dismissal

Trial ends in 5 days

Add billing details to keep fulfillment running.

Code
<Banner
  tone="info"
  title="Trial ends in 5 days"
  dismissible
  onDismiss={() => localStorage.setItem('trial-banner', 'dismissed')}
  action={<Button size="sm">Upgrade plan</Button>}
>
  Add billing details to keep fulfillment running.
</Banner>

<Banner tone="error" title="3 payouts failed" action={<Link href="#">Review payouts</Link>}>
  Bank details were rejected by the provider.
</Banner>

Because dismissal unmounts the banner, the same element will not reappear when its props change. When a new notice must surface after a prior dismissal, give the banner a key tied to the notice id (or conditionally render it) so React mounts a fresh instance.

Alignment and icons

align="center" suits short, whole-app statements such as read-only mode. Pass icon={null} when the copy carries the meaning on its own, or supply your own node for product-specific marks.

Alignment and Icons
Read-only mode — you are viewing a historical snapshot.

Impersonating operator@acme.io

Code
<Banner align="center" icon={null}>
  Read-only mode — you are viewing a historical snapshot.
</Banner>
<Banner tone="info" align="center" title="Impersonating operator@acme.io" dismissible />
<Banner tone="warning" icon={null} title="Legacy API deprecated">
  Migrate to v3 before 31 August.
</Banner>

Sticky chrome

sticky pins the banner to the top of the viewport at z-30 — above the AdminShell header — so an outage notice survives a long queue scroll. Reserve it for notices that stay true while the operator works.

Sticky Banner

Scroll this panel — the banner stays pinned.

Order #10241 · captured

Order #10242 · queued

Order #10243 · queued

Order #10244 · queued

Order #10245 · queued

Order #10246 · queued

Code
<Banner sticky tone="error" title="Payment provider degraded">
  Captures are queued and will retry automatically.
</Banner>

Props

NameTypeDefaultDescription
tone'neutral' | 'info' | 'success' | 'warning' | 'error''neutral'Severity of the notice; drives the tonal surface and the ARIA role
titleReact.ReactNode-Headline of the notice, rendered in medium weight
childrenReact.ReactNode-Supporting copy shown beneath the title
iconReact.ReactNodetone iconLeading icon slot; defaults to a decorative tone icon, pass null to suppress
actionReact.ReactNode-Trailing action slot — a Button or Link that resolves the notice
dismissiblebooleanfalseRender a dismiss button that unmounts the banner
onDismiss() => void-Called when the operator dismisses the banner; persist the choice here
dismissLabelstring'Dismiss'Accessible name for the dismiss button
stickybooleanfalsePin the banner to the top of the viewport above app chrome
align'start' | 'center''start'Horizontal alignment of the banner content
classNamestring-Additional CSS classes to apply

Accessibility

  • neutral, info, and success render role="status" (polite) so routine notices do not interrupt; warning and error render role="alert" (assertive) so urgent notices do.
  • Mount urgent banners in response to the event rather than rendering them hidden — a live region only announces content that arrives after it is on the page.
  • The dismiss button has an accessible name (dismissLabel, default "Dismiss") and the standard focus-visible ring, so it is reachable and visible via keyboard.
  • Default tone icons are aria-hidden — severity is carried by the copy and the role, never by color alone. A custom icon keeps its own semantics, so give a meaningful mark an alt or aria-label (and mark a purely decorative one aria-hidden yourself).
  • Dismissing moves focus to the document body because the button unmounts with the banner; if the banner sits mid-page, move focus somewhere deliberate in onDismiss.
  • Place the banner early in the DOM (above the header) so tab order reaches the notice and its action before the page content.

Best Practices

Do

  • Use Banner for app-wide state, and Alert for messages scoped to one form, card, or panel
  • Keep the copy to one line of title plus one line of supporting text — it is chrome, not a document
  • Pair dismissible with persistence in onDismiss so the notice does not return on every route change
  • Reserve error and warning for notices an operator must act on, so role="alert" keeps its meaning
  • Give the banner a key tied to the notice id when a new message must appear after a prior dismissal

Don't

  • Stack more than one banner — the second one is already noise; queue them or promote the urgent one
  • Use Banner for transient confirmations ("Saved") — that is Toast
  • Round the corners or add a shadow to make it look like a card; the flat bottom border is what makes it read as chrome
  • Make a banner dismissible when the condition is still true and unresolvable by the operator
  • Use sticky for notices that stop being true as the operator scrolls or navigates
  • Alert - Inline, content-scoped feedback message
  • Toast - Transient confirmation of an action
  • AdminShell - App frame the banner sits inside
  • StatusPill - Compact record-level status

On this page