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
- Add a banner
All triggers
- Inform user of system state at the top of a page
- Offer an action on a persistent issue (cookie consent, outage)
- Show an inline announcement without modality
Specification body
Spec — Banners
Facet Visual of Koder Design. Material parity: https://m3.material.io/components/banners.
Where they fit
| Component | Modality | Persistence |
|---|---|---|
| Banner | Non-modal | Persistent until user dismisses / acts |
| Snackbar | Non-modal | Transient (4-10 s) |
| Dialog | Modal | Persistent 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
| Tone | Use | Icon | Background |
|---|---|---|---|
| Info | General announcement | info (ⓘ) | surface-container-low |
| Warning | Action needed soon | warning (⚠) | warning-container |
| Error | Action needed now / degraded service | error (✕) | error-container |
| Success / promo | Positive announcement | check (✓) | 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
| Placement | When |
|---|---|
| Top of page content (default) | One banner per page |
| Top of section | One banner scoped to a section |
| Inside list / form | Inline 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) orrole="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
| Width | Behavior |
|---|---|
| ≥ 600 dp | Single row, actions trailing |
| < 600 dp | Two 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
| Density | Padding | Height (single line) |
|---|---|---|
| Compact | 12 px / 20 px | 48 px |
| Default | 16 px / 24 px | 54 px |
| Comfortable | 20 px / 28 px | 64 px |
R9 — Per-preset variation
| Preset | Visual |
|---|---|
material3 | Tonal background by tone, no border, 24 px radius corners on rounded variant |
material2 | Sharp corners, 1 px hairline border |
ios_cupertino | Top-anchored, slight translucency, dismiss as swipe-up gesture |
gnome | InfoBar style — flat tonal, 6 px border-radius |
windows_11 | Mica backdrop tint, accent color border-left |
brutalist | Solid color block, 4 px thick border, no rounded corners |
terminal_classic | Border-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)
Cross-link
themes/color-roles.kmd— tonal container tokensthemes/elevation.kmd— banner sits at 0 dp (within surface)components/snackbars.kmd— transient siblingcomponents/dialogs.kmd— modal siblingfoundations/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-containercolor 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 thewarning-containerbackground - 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-expiryis 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-expiryis 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
specs/foundations/elements.kmdspecs/themes/color-roles.kmdspecs/themes/elevation.kmdspecs/components/snackbars.kmdspecs/components/dialogs.kmd