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

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

Highlight
Fig 1.1 · Banner · Bridge view
Both

Figma↔Code mismatches

Where designer and developer worlds typically misalign on this component.

  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.

Both

Variants, properties, states

Variants

Structurally different versions of the component.

infopromotionalwarningerror

Properties

The same component, parameterised.

PropertyType
dismissibleboolean
persistentboolean
hasIconboolean
hasTitleboolean
layouthorizontal | stacked

States

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

KindStates
interactive
hoverfocus-visible
data
opendismissed
Both

Figma ↔ Code property map

FigmaTypeCodeNotes
VariantVariantvariantMaps info / promotional / warning / error. Drives visual treatment plus icon glyph plus (where applicable) accessible role.
LayoutVariantlayouthorizontal / stacked. At and below `breakpoint.sm` the layout is forced to stacked regardless of authored value.
DismissibleBooleandismissibleToggles the dismiss-button slot visibility plus the dismiss event wiring.
PersistentBooleanpersistentControls whether the dismissed state survives page reload. True for one-time announcements (cookie consent, beta feature); false for session-only banners (maintenance that resolves at session end).
Has IconBooleanhasIcon
Has TitleBooleanhasTitle
TitleTexttitle
BodyTextbody
ActionsInstance SwapactionsSwap the actions slot — typically 1–3 Button or Link instances.
IconInstance SwapiconVariant-default icon glyph; designers may swap for domain-specific icons (e.g. a beta-tag icon for beta-feature banners).
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.
Designer

Figma anatomy

Slot Figma type Hint
container frame Auto-layout horizontal frame at full inline-size; padding from container token
icon instance Icon component instance; glyph swap bound to variant
title text Heading text style; bound to a component property
body text Body text style; bound to a component property
actions frame Auto-layout horizontal frame containing button or link instances
dismiss-button instance Icon button instance; visibility bound to dismissible property
Dev

Code anatomy

Slot Code slot Semantic
container container region-or-status
icon icon presentational
title title heading-or-strong
body body prose
actions actions button-group
dismiss-button dismiss button
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

Internationalisation

RTL · mirroring

Banner inline layout follows logical direction — inline-start to inline-end ordering of icon · content · actions reverses visual position in RTL. Dismiss button moves to the visual left in RTL (still inline-end logically). Action button order reverses logically: primary action stays on inline-end (visual right in LTR, visual left in RTL). Variant icon glyphs are direction-neutral. Stacked layout is direction-neutral by inline-axis.

Text expansion

Title and body grow with translation; banner container inline-size is determined by parent (full bleed at document scope, region width at section scope), so growth wraps naturally to additional lines. Long-text languages (German, Russian, Finnish) routinely expand banner height by 20-30%; layout `stacked` accommodates better than `horizontal` for long text. Action button labels follow Button's expansion rules; multi-action banners may need to wrap actions to a second row under heavy expansion.

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.
actions Buttons keep native semantics; links keep anchor semantics. Order primary first (visually inline-end on LTR; SR announces in DOM order). For cookie banners with legal-compliance requirements, "Decline" must be as prominent as "Accept" — visual weight equality is canonical.
dismiss-button Provide an accessible name ("Dismiss banner" or "Close notification"). Activation removes the banner from the document. Persistence (re-appears on next visit vs permanently dismissed) is a `persistent` property, documented separately.
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
Both

Common mistakes

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

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

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

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