Snackbars
components specs/components/snackbars.kmd
Transient feedback message at the bottom of the screen — confirms an action, optionally offers a single action (undo, retry). Material parity (`/components/snackbars`). Distinguished from banner (persistent) and dialog (modal).
Quando esta spec se aplica
Triggers primários
- Add a snackbar
Todos os triggers
- Confirm a completed action with a brief message
- Offer 'Undo' immediately after a destructive action
- Surface a non-blocking error / status update
Corpo da especificação
Spec — Snackbars
Facet Visual of Koder Design. Material parity: https://m3.material.io/components/snackbars.
Where they fit
| Component | Modality | Persistence | Action count |
|---|---|---|---|
| Snackbar | Non-modal | Transient (4-10 s) | 0 or 1 |
| Banner | Non-modal | Persistent until dismissed | 0-2 |
| Dialog | Modal | Until dismissed | Unlimited |
Use snackbar for low-priority, transient confirmation. Switch to banner for persistent attention or dialog for blocking interaction.
Anatomy (with action)
┌────────────────────────────────────────────────┐
│ File moved to Trash. UNDO │
└────────────────────────────────────────────────┘
↑ ↑
message action
- Container width: min 344 dp / max 672 dp; on Compact width fills minus 16 px margin each side
- Height: 48 px (single line) / 68 px (two lines) / auto for longer
- Padding: 16 px horizontal, 14 px vertical
- Corner radius: 4 px
- Container bg:
inverse-surface(high contrast — light bg in dark mode, dark bg in light mode) - Message:
body-medium(14/20, weight 400), colorinverse-on-surface - Action: text button,
inverse-primarycolor, weight 600 - Elevation: 3 dp
R1 — Placement
| Surface | Position |
|---|---|
| Compact (mobile) | Bottom-centered, 16 px from bottom safe-area |
| Medium / Expanded | Bottom-left, 16 px from edges |
| Large (desktop wide) | Bottom-left (default); allow bottom-right via prop |
Snackbar floats above app content but BELOW navigation bars (bottom app bar, navigation bar). Anchor offset must account for bottom nav height + safe-area inset.
R2 — Duration
| Snackbar type | Default duration | Notes |
|---|---|---|
| Plain message (no action) | 4 s | "File saved." |
| With action (e.g., "UNDO") | 6 s | Gives user time to act |
| Long / important | 10 s | Cap at 10 s — beyond, use banner |
| Indefinite | Off by default | Only when user must act (rare) |
Pause timer on hover / focus (resume when they leave). An Indefinite snackbar has no timer at all and MUST show a close × so the user can dismiss it manually.
Reduced motion (prefers-reduced-motion: reduce) removes the slide
animation only (see R6); the auto-dismiss timing is unchanged — the
snackbar still auto-dismisses on its normal duration. Reduced motion
is a vestibular preference (no motion), not a timing one, so it
does not disable auto-dismiss and does not add a close ×. The close ×
affordance belongs to the Indefinite / error / two-line variants
(R4), never to the reduced-motion state.
R3 — Actions
- 0 actions: snackbar acts as confirmation; tap snackbar dismisses
- 1 action: text button on right (UNDO, RETRY, VIEW); tap action triggers callback + dismisses
- 2+ actions: NEVER. Switch to dialog or banner.
Action labels:
- Single-word imperative when possible: UNDO, RETRY, VIEW, DISMISS
- All caps OR Title Case — pick one style per app and stick to it
- Color:
inverse-primary(high contrast against inverse surface)
R4 — Two-line variant
When message is longer than fits on one line:
┌────────────────────────────────────────────────┐
│ Sync failed. Check your network and retry. │
│ RETRY × │
└────────────────────────────────────────────────┘
- Action moves to second line, right-aligned
- Optional close × button (added for error / important snackbars)
- Container grows to 68 px
Never exceed 2 lines. Truncate or escalate to banner / dialog.
R5 — Queue behavior
Multiple snackbars: queue them.
- Current snackbar finishes its duration (or user dismisses) → next pops up
- New snackbar with same content while a same-content one is showing: reset its timer (don't queue duplicate)
- Maximum visible: ALWAYS 1 — never stack snackbars
Programmatic dismissal: API to dismiss current immediately (e.g., on route change to clear stale snackbars).
R6 — Animation
- Enter: slide-in from bottom edge (motion-medium, ~250 ms emphasized)
- Exit: slide-out down + fade (motion-fast, ~150 ms)
- Action tap: snackbar fades while action runs
- Reduced motion: instant in / out; no slide
R7 — Dismissal
| Trigger | Result |
|---|---|
| Timer elapses | Auto-dismiss |
| User taps snackbar (no action variant) | Dismiss |
| User taps action button | Run action + dismiss |
| User taps × (two-line variant) | Dismiss (no action) |
| User swipes (touch) | Dismiss (mobile gesture) |
| Esc key (keyboard focus on snackbar) | Dismiss |
| Programmatic | Dismiss |
R8 — Accessibility
- Container:
role="status"(info) ORrole="alert"(error) aria-live="polite"(status) /assertive(alert)- Action button: clear label ("Undo move to trash", not just "Undo" if context is unclear)
- Close × button:
aria-label="Dismiss" - Screen reader announces full snackbar content + action label on appearance
- Don't auto-focus snackbar (would steal focus from user task)
- Don't rely on color (action is also bold text + button hit target)
R9 — Tone
Snackbars are intentionally neutral in tone. Don't tonally style with
error / warning / success backgrounds — that's banner territory.
Snackbar bg is always inverse-surface (high-contrast neutral).
If you need to express tone (error, warning), use a banner or dialog.
R10 — Density
| Density | Height (single) | Padding |
|---|---|---|
| Compact | 40 px | 12 px / 8 px |
| Default | 48 px | 16 px / 14 px |
| Comfortable | 56 px | 20 px / 18 px |
R11 — Per-preset variation
| Preset | Container | Action color |
|---|---|---|
material3 | inverse-surface 4 px radius | inverse-primary |
material2 | Dark gray 2 px radius | Accent color |
ios_cupertino | Rounded toast with blur backdrop | System blue |
gnome | Adwaita In-app notification, slide from top | Accent |
windows_11 | Mica notification toast | Accent |
brutalist | Sharp black bg, white text | Inverted action button |
terminal_classic | Single-line [msg] <UNDO> at bottom | Underlined action |
R12 — Forbidden patterns
- ❌ More than 1 action button
- ❌ Stacking snackbars (queue or replace, never stack)
- ❌ Snackbar with > 2 lines of text
- ❌ Snackbar > 10 s (use banner instead)
- ❌ Snackbar < 4 s (too brief to read for slow / impaired readers)
- ❌ Auto-focus / focus trap (would interrupt user)
- ❌ Tonal bg color (use neutral
inverse-surface) - ❌ Snackbar with form inputs / interactive widgets (snackbar is for confirmation, not input)
- ❌ Snackbar that requires reading to use the app (use dialog if blocking)
- ❌ Snackbar without safe-area bottom inset on iOS / Android gesture-nav
Cross-link
themes/elevation.kmd— 3 dp tonal + shadow recipethemes/color-roles.kmd—inverse-surface/inverse-on-surface/inverse-primarytokensthemes/motion.kmd— emphasized easing for enterthemes/typography.kmd—body-mediumfor messageapp-layout/safe-area.kmd— bottom inset rulecomponents/banners.kmd— persistent siblingcomponents/dialogs.kmd— modal siblingfoundations/elements.kmd— Marker family
Requirements (testable)
Requirement: At most one action button {#req-snackbar-single-action}
A snackbar SHALL expose at most one action button; a request to render two or more actions SHALL be refused (escalate to a dialog or banner instead), and tapping the single action SHALL invoke its callback and dismiss the snackbar.
Scenario: Single action runs callback and dismisses
- GIVEN a snackbar rendered with one action labelled "UNDO"
- WHEN the user taps the "UNDO" action button
- THEN the action callback fires exactly once
- AND the snackbar dismisses
Scenario: Two actions are rejected
- GIVEN a snackbar configured with two actions ("UNDO" and "RETRY")
- WHEN the snackbar is requested to render
- THEN at most one action button is rendered
- AND the second action is not stacked (the request is refused, directing the caller to a dialog or banner)
Requirement: Duration bounds and timer pause {#req-snackbar-duration}
A snackbar's auto-dismiss duration SHALL be at least 4 s and at most 10 s (plain 4 s, with-action 6 s), and the auto-dismiss timer SHALL pause while the snackbar has hover or focus and resume when they leave.
Scenario: With-action snackbar auto-dismisses after its duration
- GIVEN a snackbar with an action shown with the default 6 s duration and no hover or focus
- WHEN 6 s of unpaused time elapse
- THEN the snackbar auto-dismisses
Scenario: Hover pauses the timer
- GIVEN a plain snackbar with a 4 s duration that has been visible for 3 s
- WHEN the pointer hovers over the snackbar for a further 5 s
- THEN the snackbar remains visible (the timer is paused, not elapsed)
- AND it resumes counting only after the pointer leaves
Requirement: Reduced-motion removes animation only {#req-snackbar-reduced-motion}
Under prefers-reduced-motion: reduce a snackbar SHALL NOT play the slide animation and SHALL appear/disappear instantly, while its auto-dismiss timing SHALL remain unchanged — reduced motion is a vestibular preference, not a timing one, so it SHALL NOT disable auto-dismiss and SHALL NOT add a close ×. The close × affordance is governed by the variant (Indefinite / error / two-line), not by the reduced-motion preference.
Scenario: Reduced motion removes the slide but keeps auto-dismiss
- GIVEN the environment reports
prefers-reduced-motion: reduce - WHEN a plain snackbar with a 4 s duration is shown with no hover or focus
- THEN it appears without a slide-in animation
- AND it still auto-dismisses after its 4 s duration
Scenario: Indefinite snackbar shows a close affordance
- GIVEN an indefinite snackbar (no auto-dismiss timer)
- WHEN it is shown
- THEN a close × button is present for manual dismissal
- AND the affordance is present because the variant is indefinite, not because of any motion preference
Requirement: Never stack; queue or coalesce {#req-snackbar-queue}
At most one snackbar SHALL be visible at any time; additional snackbars SHALL be queued and shown only after the current one is dismissed, and a new snackbar whose content matches the one currently showing SHALL reset that snackbar's timer instead of enqueuing a duplicate.
Scenario: Second distinct snackbar waits in queue
- GIVEN a snackbar A is currently visible
- WHEN a second snackbar B with different content is requested
- THEN exactly one snackbar (A) is visible
- AND B is shown only after A is dismissed
Scenario: Duplicate content resets the timer
- GIVEN a snackbar showing "File saved." that has been visible for 3 s
- WHEN another "File saved." snackbar is requested
- THEN no second snackbar is enqueued
- AND the visible snackbar's auto-dismiss timer is reset to its full duration
Requirement: Dismissal triggers {#req-snackbar-dismissal}
A snackbar SHALL be dismissible by timer elapse, by tapping the action button (which also runs the action), by the close × on the two-line variant, by touch swipe, by the Esc key when it holds keyboard focus, and by programmatic call.
Scenario: Esc key dismisses a focused snackbar
- GIVEN a snackbar that currently holds keyboard focus
- WHEN the user presses the Esc key
- THEN the snackbar dismisses
Scenario: Programmatic dismiss clears the current snackbar
- GIVEN a snackbar is visible
- WHEN the app calls the dismiss API (e.g., on route change)
- THEN the current snackbar is removed immediately
- AND no snackbar remains visible
Requirement: Accessibility semantics and focus {#req-snackbar-a11y}
A snackbar SHALL expose role="status" with aria-live="polite" for info and role="alert" with aria-live="assertive" for error, its close × SHALL carry aria-label="Dismiss", and it SHALL NOT auto-focus or trap focus so the user's current task is not interrupted.
Scenario: Error snackbar announces assertively
- GIVEN an error snackbar is rendered
- WHEN it appears
- THEN its container exposes
role="alert"witharia-live="assertive" - AND its content plus action label are available to the screen reader
Scenario: Appearance does not steal focus
- GIVEN keyboard focus is on a text input in the user's task
- WHEN a snackbar appears
- THEN focus remains on the text input
- AND the snackbar neither auto-focuses nor creates a focus trap
Referências
specs/foundations/elements.kmdspecs/themes/color-roles.kmdspecs/themes/elevation.kmdspecs/themes/motion.kmdspecs/themes/typography.kmdspecs/components/banners.kmdspecs/components/dialogs.kmd