Snackbars

mandatory

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

Spec — Snackbars

Facet *isual*of Koder Design. Material parity: https://m3.material.io/components/snackbars.

Where they fit

Component Modality Persistence Action count
*nackbar* Non-modal Transient (4-10 s) 0 or 1
*anner* Non-modal Persistent until dismissed 0-2
*ialog* 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
  • *ontainer width* min 344 dp / max 672 dp; on Compact width

    fills minus 16 px margin each side

  • *eight* 48 px (single line) / 68 px (two lines) / auto for

    longer

  • *adding* 16 px horizontal, 14 px vertical
  • *orner radius* 4 px
  • *ontainer bg* inverse-surface (high contrast — light bg in

    dark mode, dark bg in light mode)

  • *essage* body-medium (14/20, weight 400), color

    inverse-on-surface

  • *ction* text button, inverse-primary color, weight 600
  • *levation* 3 dp

R1 — Placement

Surface Position
Compact (mobile) Bottomcentered, 16 px from bottom safearea
Medium / Expanded Bottom-left, 16 px from edges
Large (desktop wide) Bottomleft (default); allow bottomright 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 / prefers-reduced-motion: reduce (no auto-dismiss in that case — show close × instead).

Reduced motion: still auto-dismisses, but no slide animation.

R3 — Actions

  • * actions* snackbar acts as confirmation; tap snackbar dismisses
  • * action* text button on right (UNDO, RETRY, VIEW); tap action

    triggers callback + dismisses

  • *+ 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

  • *nter* slidein from bottom edge (motionmedium, ~250 ms

    emphasized)

  • *xit* slideout down + fade (motionfast, ~150 ms)
  • *ction tap* snackbar fades while action runs
  • *educed 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) OR role="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

  • themes/elevation.kmd — 3 dp tonal + shadow recipe
  • themes/color-roles.kmdinverse-surface / inverse-on-surface /

    inverse-primary tokens

  • themes/motion.kmd — emphasized easing for enter
  • themes/typography.kmdbody-medium for message
  • app-layout/safe-area.kmd — bottom inset rule
  • components/banners.kmd — persistent sibling
  • components/dialogs.kmd — modal sibling
  • foundations/elements.kmd — Marker family

Source: ../home/koder/dev/koder/meta/docs/stack/specs/components/snackbars.kmd