Skip to content

Banners

components specs/components/banners.kmd

Persistent informational bar at the top of a content region — carries a message and 1-2 actions. Material parity (`/components/banners`). Distinguished from snackbars (transient) and dialogs (modal); banners stay until dismissed and never block interaction.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Banners

Facet Visual of Koder Design. Material parity: https://m3.material.io/components/banners.

Where they fit

ComponentModalityPersistence
BannerNon-modalPersistent until user dismisses / acts
SnackbarNon-modalTransient (4-10 s)
DialogModalPersistent until user dismisses

Pick banner when: information requires action OR awareness over multiple page interactions, but does NOT need to block the user.

Anatomy

┌────────────────────────────────────────────────────────┐
│  ⓘ  Your subscription expires in 3 days.               │
│      Renew now to keep access.                         │
│                                  [Renew]   [Dismiss]   │
└────────────────────────────────────────────────────────┘
   ↑   ↑                              ↑         ↑
 icon  message                      action1   action2
  • Height: 54 px (single line) / 90 px (two lines) / auto
  • Padding: 16 px vertical, 24 px horizontal
  • Icon: 24 px, optional, leading
  • Message: body-medium (14/20, weight 400)
  • Actions: text buttons, trailing
  • Border: 1 px bottom outline-variant
  • Background: surface-container-low

R1 — Variants by tone

ToneUseIconBackground
InfoGeneral announcementinfo (ⓘ)surface-container-low
WarningAction needed soonwarning (⚠)warning-container
ErrorAction needed now / degraded serviceerror (✕)error-container
Success / promoPositive announcementcheck (✓)success-container (extended)

Color alone never carries meaning — always pair tone with icon + text.

R2 — Action rules

  • 0 actions: dismiss-only banner; tap × at end OR auto-show dismiss after read
  • 1 action: action + dismiss button (× icon at end)
  • 2 actions: primary + secondary text buttons; no separate × (secondary serves as dismiss when labeled "Dismiss")

Maximum 2 visible actions. Don't put overflow menu in a banner.

R3 — Placement

PlacementWhen
Top of page content (default)One banner per page
Top of sectionOne banner scoped to a section
Inside list / formInline banner about a specific item

Never stack multiple banners. If multiple announcements pending, show the highest-priority one first; queue the rest after dismissal.

App bar sits ABOVE banner — banner anchors below the top app bar.

R4 — Persistence and state

  • Banner stays visible across scroll (does not stick to viewport; scrolls with content)
  • Dismissal is REMEMBERED per user + per banner ID — don't re-show the same dismissed banner unless conditions change
  • For "cookie consent" or "system upgrade" type banners, document re-show conditions explicitly

R5 — Animation

  • Appear: slide-down from top edge (motion-medium, ~250 ms)
  • Dismiss: fade-out + slide-up (motion-fast, ~150 ms)
  • Content below shifts smoothly when banner appears / disappears (no abrupt layout jump)
  • Reduced motion: instant show / hide

R6 — Accessibility

  • role="status" (info) or role="alert" (warning/error)
  • aria-live="polite" (status) / assertive (alert)
  • Action buttons have full labels (not "Click here")
  • Dismiss × button: aria-label="Dismiss banner"
  • Keyboard: Tab into action buttons → dismiss button; Esc dismisses (when allowed by banner type)
  • Icon is decorative if message conveys tone in words — aria-hidden

R7 — Mobile responsiveness

WidthBehavior
≥ 600 dpSingle row, actions trailing
< 600 dpTwo rows: message on top, actions on second row (left-aligned)

Truncate to 2 lines max on Compact width; expand to fit when actions move to a separate row.

R8 — Density

DensityPaddingHeight (single line)
Compact12 px / 20 px48 px
Default16 px / 24 px54 px
Comfortable20 px / 28 px64 px

R9 — Per-preset variation

PresetVisual
material3Tonal background by tone, no border, 24 px radius corners on rounded variant
material2Sharp corners, 1 px hairline border
ios_cupertinoTop-anchored, slight translucency, dismiss as swipe-up gesture
gnomeInfoBar style — flat tonal, 6 px border-radius
windows_11Mica backdrop tint, accent color border-left
brutalistSolid color block, 4 px thick border, no rounded corners
terminal_classicBorder-style box ┌── INFO ───┐ ... └──[OK]─┘

R10 — Forbidden patterns

  • ❌ Stacking multiple banners on one page
  • ❌ Banner over modal (banner is non-modal; modal already blocks)
  • ❌ Auto-dismissing banner with a timer (use snackbar instead)
  • ❌ More than 2 actions
  • ❌ Putting form inputs / interactive controls inside banner body
  • ❌ Showing the same dismissed banner without user-visible state change
  • ❌ Color-only tone signal (always pair with icon + words)
  • ❌ Sticking banner to viewport on scroll (annoying; use snackbar if must stay visible)
  • themes/color-roles.kmd — tonal container tokens
  • themes/elevation.kmd — banner sits at 0 dp (within surface)
  • components/snackbars.kmd — transient sibling
  • components/dialogs.kmd — modal sibling
  • foundations/elements.kmd — Container + Marker families

Requirements (testable)

Requirement: Tone always paired with icon and text {#req-banners-tone-non-color}

A banner SHALL convey its tone (info / warning / error / success) through the tone's icon and message text in addition to background color; color alone SHALL NOT be the only signal of tone.

Scenario: Error tone renders icon plus color

  • GIVEN a banner configured with tone error
  • WHEN the banner is rendered
  • THEN the banner background uses the error-container color role
  • AND a leading error (✕) icon is present
  • AND the message text is displayed alongside the icon

Scenario: Tone is not signaled by color only

  • GIVEN a banner configured with tone warning
  • WHEN the rendered banner is inspected
  • THEN a warning (⚠) icon accompanies the warning-container background
  • AND with color removed (grayscale render) the ⚠ icon and message text are still present to identify the tone

Requirement: Action count bounded to two {#req-banners-action-limit}

A banner SHALL display at most two visible actions and SHALL NOT contain an overflow menu or more than two action controls.

Scenario: Two actions render primary and secondary buttons

  • GIVEN a banner configured with a primary and a secondary text button
  • WHEN the banner is rendered
  • THEN exactly two trailing text buttons are shown
  • AND no separate dismiss × icon is rendered

Scenario: A third action is rejected

  • GIVEN a banner configuration requesting three actions
  • WHEN the banner is built
  • THEN construction fails or the banner renders no more than two action controls
  • AND no overflow menu is present

Requirement: Never stack banners on one page {#req-banners-no-stacking}

A page or scoped region SHALL show at most one banner at a time; when multiple announcements are pending it SHALL show the highest-priority one first and queue the rest for after dismissal.

Scenario: Second pending banner is queued

  • GIVEN two pending banners of differing priority targeting the same page
  • WHEN the banners are presented
  • THEN only the highest-priority banner is visible
  • AND the second banner is not rendered simultaneously

Scenario: Queued banner appears after dismissal

  • GIVEN a visible banner and one queued lower-priority banner
  • WHEN the visible banner is dismissed
  • THEN the queued banner becomes visible
  • AND still only one banner is shown at a time

Requirement: Dismissal is remembered per user and banner ID {#req-banners-dismissal-memory}

A dismissed banner SHALL be remembered per user and per banner ID and SHALL NOT be re-shown to that user unless the documented re-show conditions change.

Scenario: Dismissed banner stays hidden on revisit

  • GIVEN a user who has dismissed the banner with ID sub-expiry
  • WHEN the same page is revisited with unchanged conditions
  • THEN the banner with ID sub-expiry is not shown again

Scenario: Changed condition re-shows the banner

  • GIVEN a user who previously dismissed banner ID sub-expiry
  • WHEN a documented re-show condition changes
  • THEN the banner with ID sub-expiry is shown again

Requirement: Accessible roles, labels and Esc dismissal {#req-banners-a11y}

A banner SHALL expose role="status" with aria-live="polite" for info tone and role="alert" with aria-live="assertive" for warning/error tone, SHALL give the dismiss × button aria-label="Dismiss banner", and SHALL let Esc dismiss the banner when the banner type permits dismissal.

Scenario: Error banner announces assertively

  • GIVEN a banner with tone error
  • WHEN the banner is rendered
  • THEN its root has role="alert"
  • AND aria-live="assertive"

Scenario: Dismiss button is labeled and Esc dismisses

  • GIVEN a dismissible banner with focus inside its action region
  • WHEN the user presses Esc
  • THEN the banner is dismissed
  • AND the dismiss × control exposes aria-label="Dismiss banner"

Requirement: Reduced motion disables banner animation {#req-banners-reduced-motion}

Banner appearance and dismissal SHALL animate (slide-down on appear, fade-out plus slide-up on dismiss), but when reduced motion is requested the banner SHALL show and hide instantly with no transition.

Scenario: Normal appearance slides down

  • GIVEN reduced motion is not requested
  • WHEN a banner appears
  • THEN it animates a slide-down from the top edge over roughly 250 ms

Scenario: Reduced motion shows instantly

  • GIVEN the user has reduced motion enabled
  • WHEN a banner appears or is dismissed
  • THEN it toggles visibility instantly with no slide or fade transition

References