Dev view

Banner

A region-wide persistent informational surface — typically pinned to the top of a page or section — that surfaces system-level notices, promotional content, or status messages that should be visible across the user's session. Distinct from Alert (contextual, bound to a specific situation) by its persistence and scope, from Toast (transient, corner-anchored) by its persistence and full width, and from Modal (blocking) by its non-blocking contract. Used for cookie consent, maintenance notices, beta-feature callouts, system-degradation warnings, and full-bleed promotional CTAs.

Also called Inline alert Notification bar

When to use

Use

For region-wide or page-wide persistent informational surfaces — cookie consent, maintenance notices, beta-feature callouts, system-degradation warnings, full-bleed promotional CTAs. Banner is non-blocking, persistent (survives navigation when configured), and visible across the user's session.

Avoid

For contextual situation-specific notices bound to a specific page or form — that is `Alert`. For transient confirmations after async actions — that is `Toast`. For blocking decisions or destructive confirmations — that is `Modal[variant=alertdialog]`. For on-demand description — that is `Tooltip`.

Versus related

  • alert

    `Alert` is contextual and bound to a specific situation (form error, recent action confirmation); `Banner` is page-wide / region-wide and persists across the session. Alert appears in response to an event; Banner persists as ongoing system signal.

  • toast

    `Toast` is corner-anchored, ephemeral, and action-triggered; `Banner` is region-wide, persistent, and system-pushed. They occupy opposite ends of the notification-permanence spectrum.

  • modal

    `Modal[variant=alertdialog]` is blocking and demands explicit response; `Banner` is non-blocking and persistent. Banner that needs to block the page is a redesign signal (use Modal); Modal that needs to persist across navigation is also a redesign signal (use Banner plus an in-page Modal triggered from it).

Banner is a page-level announcement strip anchored to the layout — typically at the top or bottom of the viewport, rarely both. It persists until user dismissal or programmatic close and never traps focus, distinguishing it from Modal interruptions and inline Alerts. Banners carry status messages, system-wide notices, consent prompts, and maintenance windows. The reference documents the canonical anatomy, the dismissal contract, the severity vocabulary, and the GDPR-region consent pattern that lives at the canon edge.

Highlight
Fig 1.1 · Banner · Dev view
Dev

Code anatomy

Slot Code slot Semantic
container container region-or-status
icon icon presentational
title title heading-or-strong
body body prose
primary-action from action-group primary-action button
secondary-action from action-group secondary-action button
tertiary-action from action-group tertiary-action button
close-button from close-button close-button button
close-icon from close-button close-icon img-decorative
close-label from close-button close-label visually-hidden
Both

Variants, properties, states

Variants

Structurally different versions of the component.

info promotional warning aka caution · attention error aka danger · destructive · critical

Properties

The same component, parameterised.

PropertyType
dismissible boolean
persistent boolean
hasIcon boolean
hasTitle boolean
layout horizontal | stacked

States

Browser/user-driven (interactive) vs. app-driven (data).

KindStates
interactive
hoverfocus-visible
data
opendismissed
Both

State transitions

FromToTrigger
opendismissedUser clicks the dismiss button (when dismissible: true); or the consumer programmatically removes the banner; or the underlying system state resolves (e.g. maintenance window ends) and the consumer rehides the banner.
Dev

Cross-framework expression

FrameworkStructure mechanismVariant mechanism
Web Components A `<ui-banner>` host with named slots for `icon` / `title` / `body` / `actions`; container `[role]` reflected from the `severity` attribute when applicable attributes (`variant="warning"`, `dismissible`, `persistent`, `layout="stacked"`); `data-state="open|dismissed"` for CSS
React Single `<Banner>` component with `variant` prop; compound subcomponents (`Banner.Icon`, `Banner.Title`, `Banner.Body`, `Banner.Actions`) or named props for content props with class-variance-authority for variant / layout; `dismissible` boolean drives close button; `onDismiss` callback
Angular (signals) `<ui-banner>` component with content projection for icon / title / body / actions; host bindings drive `[attr.role]` input<'info' | 'promotional' | 'warning' | 'error'>(); `[dismissible]`, `[persistent]` host bindings; `output('dismiss')`
Vue Single `<Banner>` SFC with named slots for icon / title / body / actions; the `variant` prop drives visual treatment defineProps with literal-union types; emits `dismiss`
Both

Events

  1. dismiss
    Payload
    `{ source: 'closeButton' | 'programmatic' }`. Distinguishes a user-clicked dismiss from a consumer-triggered programmatic removal. For `persistent: true` banners, the dismiss event is the signal for the consumer to write the dismissed flag to localStorage.
    Web Components
    `dismiss` CustomEvent on the host with `event.detail = { source }`.
    React
    `onDismiss(source)` callback. Some libraries pass the original DOM event; consumers wrap to recover the canonical contract.
    Angular Signals
    `output<DismissSource>('dismiss')`.
    Vue
    `@dismiss` event with payload `{ source }`.
  2. actionInvoke
    Payload
    `{ actionId: string }` — the canonical id of the invoked action. Consumer handles the effect (navigation, modal open, in-page state change). For cookie consent banners specifically, the canonical actionIds are `accept`, `decline`, `customise`.
    Web Components
    `actionInvoke` CustomEvent with `event.detail = { actionId }`.
    React
    `onAction(actionId)` callback or per-action `onClick` on the action elements.
    Angular Signals
    `output<string>('actionInvoke')`.
    Vue
    `@action-invoke` event with payload `{ actionId }`.
Both

Accessibility

Slot Accessibility hint
container Apply `role="region"` with an `aria-labelledby` reference to the title (or `aria-label` when no title is shown). For banners conveying urgent system state, `role="status"` (polite) is acceptable — `role="alert"` (assertive) is almost never appropriate for banner-scope content because the message is not action-required at the moment of appearance. Banner persists across navigation; do not re-announce on every route change.
icon Decorative — `aria-hidden="true"`. Variant intent is communicated through visible text and (where applicable) through `role`. Never rely on the icon glyph alone.
title Use a real heading element of an appropriate level relative to the surrounding document outline. The container's `aria-labelledby` references the title's id when present.
body Plain prose. Inline links (e.g. "Read our cookie policy") are valid in body prose; complex actions belong in the actions slot for keyboard reachability.
primary-action Real button or link with accessible name. First in DOM order; visual position is inline-end via logical properties. For cookie banners, the Decline action (secondary) must carry visual weight equal to Accept.
secondary-action Real button with accessible name. Second in DOM order; visually inline-start of the primary action in LTR.
tertiary-action Real button or link with accessible name. Third in DOM order.
close-button Real <button type="button"> with an accessible name. Pick one of: aria-label="Close" OR a visually-hidden <span> child — declaring both is duplicative announcement. The Escape key must trigger the same action when the host surface is dismissible.
close-icon aria-hidden="true" on the SVG. Never give the icon its own accessible name (title attribute, <title> child, or aria-label) — that double-announces alongside the host's name.
close-label Visually-hidden <span> "Dismiss banner" or "Close notification" inside the button, OR aria-label on the host with the same text. Activation removes the banner from the document for the current session.
Both

Accessibility acceptance

Keyboard walk

KeysExpected
TabFocus moves through the banner content in document order — inline links in body first, then actions in DOM order, then dismiss button. Continued Tab leaves the banner region into the rest of the page.
Enter or Space (focus on action button)Activates the action; consumer handler runs. For navigation-style actions (Link), Enter only.
Enter or Space (focus on dismiss button)Dismisses the banner. Focus moves to the next document focusable; for banners pinned at document scope, focus often lands on the main content's first focusable.
F6 (when supported)Cycles focus to landmark regions including the banner region. Allows keyboard users to reach the banner without tabbing through earlier content.

Screen-reader announcements

TriggerExpected
Banner mounts (initial page load with persistent dismissed-state false)SR announces the region's accessible name (via `aria-labelledby` to the title or `aria-label`) followed by the body content when the user navigates to the region. Banner is `role="region"` — does not auto-fire announcements (unlike Alert / Toast). Users discover banners by landmark navigation.
Banner mounts after page load (e.g. delayed system-state alert)Promote to `role="status"` for delayed-mount banners that the user should be aware of without active navigation. Keep polite (no interrupt). Avoid `role="alert"` — banners are not assertive by canon.
Banner dismissedSR users hear no "dismissed" announcement; the silence and removal of the region confirm the banner is gone. Subsequent landmark navigation skips the dismissed region.

axe-core rules to assert

  • aria-allowed-role
  • aria-required-attr
  • color-contrast
  • landmark-unique
  • region
  • role-img-alt

Same data as JSON for direct ingestion into Playwright + @axe-core/playwright or Jest + jest-axe: /api/components/banner/a11y-fixture.json

Both

Contracts

Non-negotiable contracts

  1. CanonGDPR Art. 7 / EDPB Guidelines 05/2020 (region-binding)

    Cookie-consent banners offer a Decline option of equal prominence to Accept (or an equivalent rejection path with no coercive design).

    Asymmetric prominence ("Accept" highlighted, "Decline" buried or hidden) is a legal-compliance violation in GDPR jurisdictions and a documented dark pattern globally. Implementation audits flag libraries that do not enforce the equal-prominence affordance.

Vocabulary drift

APG
Region / Status
Naming depends on urgency level.
Polaris
Banner
Material 3
Banner
Carbon
Notification
Atlassian
Inline message
Part of the inline-message family.
Dev

Common mistakes

Blocker

#banner-color-only-variant

Variant differentiated by colour alone

Problem

The banner's variant is communicated only through background or border colour. Colour-vision deficient users cannot distinguish info from warning; the icon glyph and the text both fail to name the variant.

Fix

Triple-layer variant: visual (background / border / icon), explicit prose (the title or body names the variant — "Maintenance:", "Beta:", "Important:"), and where applicable accessible role. Colour alone is never the only differentiator.

Major

#banner-as-modal

Banner sized to block the entire viewport

Problem

The banner is rendered at `100vh` or `position: fixed` with full-viewport coverage. Visually blocks the page; user cannot reach content beneath without dismissing or activating. The pattern claims Banner semantics (`role="region"`) while behaving like Modal.

Fix

Banner is a horizontal strip — top, bottom, or section- anchored — that does not visually block content beneath. If the surface genuinely needs to block, redesign as Modal with proper focus trap and `aria-modal`. Do not stretch Banner past one-to-three content rows.

Minor

#banner-multiple-stacked

Multiple banners stacked at the page top

Problem

Page renders cookie banner, beta-feature banner, maintenance banner, and promotional banner all at once. Visual hierarchy collapses; users either tune all out or miss the most important one (legal-compliance buried under promotional).

Fix

Cap the canonical banner display at 1 per scope at a time. Queue or prioritise by category: legal-compliance > system- state > beta-feature > promotional. Render the highest- priority banner; show others only after the higher- priority ones are acknowledged or dismissed.

Minor

#banner-dismissed-state-not-persisted

Dismissed banner reappears on every page load

Problem

User dismisses the banner; on the next navigation it reappears. The dismiss action feels meaningless; users learn to ignore the dismiss button entirely.

Fix

Persist the dismissed state for `persistent: true` banners — typically via localStorage keyed by banner id and version. Re-show only when the banner content changes (version bump) or when the consumer explicitly resets the dismissed state. For `persistent: false` banners (session- only), document the in-memory persistence contract.

Figma↔Code mismatches
  1. 01
    Figma

    A banner drawn full-screen, blocking the page

    Code

    A banner with `position: fixed` covering the viewport

    Consequence

    Designers may scale the banner to dominate the page for visual impact. Implementations following the Figma file ship a banner that visually blocks content, hides the navigation, and traps the user in a CTA they may not want to engage with. The pattern collapses to Modal but without Modal's a11y contract.

    Correct

    Banner is a region-wide horizontal strip, not a full-screen surface. If the surface needs to block the page, redesign as Modal (with proper focus trap and a11y contract). The canonical Banner caps height at one-to-three rows of content; tall banners are a redesign signal.

  2. 02
    Figma

    Cookie consent banner drawn with only an "Accept all" button

    Code

    Cookie consent banner with Accept-only — no Decline or Customise

    Consequence

    Designers may treat cookie banners as marketing surfaces with single CTA. Implementations following the design fail GDPR / ePrivacy compliance — users must be able to decline with a single action equally prominent as Accept.

    Correct

    For cookie consent banners specifically, the canonical action set requires Accept and Decline as visually equal. Customise / preferences may be a third option but never replaces Decline. Document the legal-compliance constraint explicitly; design review enforces it.

  3. 03
    Figma

    Banner sticky at top with no skip-link

    Code

    Banner pinned to viewport top with `position: sticky` consuming Tab focus

    Consequence

    Designers draw a sticky banner that visually persists across scroll. Developers ship it; keyboard users reach the banner first on every page-level Tab cycle (when banner contains tabbable content), and the visual sticky behaviour interacts with browser autoscroll-on-focus unpredictably.

    Correct

    Sticky banners require a skip-to-content link as the first focusable element after the banner (or before, depending on focus order). The canonical document-level skip-link already covers banners pinned at document scope; banners scoped to a region need a region-level skip-link.

  4. 04
    Figma

    Multiple banners stacked at the page top

    Code

    Multiple banners rendered, each in its own region

    Consequence

    Designers stack banners (cookie + beta-feature + maintenance + promotional) for visual mocks. Developers ship the stack; the page header is now half-banner, the user is overwhelmed, and the most recently-added banner is buried beneath legacy ones.

    Correct

    Cap the canonical banner stack at 1 visible banner at a time per scope (document-scope or region-scope). Beyond that, queue banners by priority — system-state banners (maintenance) outrank promotional banners; legal-compliance banners (cookie consent) outrank everything until acknowledged.