Skip to content

Notifications & Feedback

A UX principle for coding agents. Also covers toasts, snackbars, banners, badges, feedback, alerts, and 2 more.

Show all 8 aliases

toasts, snackbars, banners, badges, feedback, alerts, completion feedback, success ending

Every notification spends a piece of the user's attention. That attention is a budget, and an interface that interrupts for everything goes bankrupt fast — users learn to dismiss without reading, and the one message that mattered gets swiped away with the noise. The craft is matching each message to the lightest channel that still delivers it.

Feedback and notifications are the same spectrum at different weights: a tiny inline checkmark and a full-screen blocking alert both tell the user "here's what happened." The decision is never "should I notify" — it's "through which channel, at what cost to attention."

Match the Channel to the Message

Pick the channel by two axes: how urgent the message is, and whether the user must act on it. Start at the quietest option and escalate only when the message demands it.

ChannelWeightUse forBlocks?Persists?
InlineLightestFeedback tied to one control or fieldNoWhile relevant
Badge / countLightAmbient status, unread countsNoUntil cleared
ToastLight–mediumTransient confirmation or non-blocking failureNoAuto-dismiss
BannerMediumPage-wide status, system stateNoUntil resolved/dismissed
InboxMediumAsync, durable, catch-up laterNoUntil read
Modal alertHeaviestMust-acknowledge, irreversible decisionsYesUntil dismissed

The rule: never use a heavier channel than the message needs. A successful save is an inline checkmark or a toast, never a modal. A "your account will be deleted" confirmation is a modal, never a toast the user can miss by looking away.

Toasts & Snackbars

A toast is a transient, non-blocking message that appears, lingers briefly, and leaves on its own. It's the workhorse for "that worked" and "that didn't, but you're not blocked."

  • Duration scales with reading time and importance. Short confirmations: ~3–4s. Messages with an action (Undo): ~5–8s, so the user can reach the button. Errors the user should register: longer, or until dismissed. Never auto-dismiss something the user must act on.
  • Position consistently. Pick one corner (commonly bottom-center on mobile, bottom-right or top-right on desktop) and keep every toast there. A toast that appears somewhere new each time can't be found by habit.
  • Stack, with a limit. Show at most 3–4 at once; collapse or queue the rest. A tower of toasts is just a second, worse inbox.
  • Make destructive confirmations actionable with Undo. "Message archived · Undo" is far better than a confirmation dialog before the fact — it keeps the flow fast and still safe (pairs with optimistic updates, see interactions.md — Optimistic Updates). Give Undo a real window before the action commits.
  • Pair a failure toast with its cause. "Couldn't copy link" should appear near or clearly reference the copy action, not float context-free.
  • Don't put critical information only in a toast. Toasts vanish. If the user must retain it, also reflect it in a durable surface (the updated UI, a banner, the inbox).
// Actionable toast: optimistic archive with an Undo window
function archiveMessage(id: string) {
  optimisticallyArchive(id); // hide immediately
  toast("Message archived", {
    action: { label: "Undo", onClick: () => restore(id) },
    duration: 6000, // long enough to reach "Undo"
  });
}

Toasts announce themselves to screen readers through a polite live region (see Accessibility below) — they should never steal focus, because that would interrupt whatever the user is doing for a message defined as non-blocking.

Inline & Contextual Feedback

The quietest and often best feedback appears right where the action happened, on the thing itself. It needs no channel and no attention tax because the user is already looking there.

  • A copy button that becomes a checkmark, a save indicator next to the field, a row that briefly highlights after editing — these confirm without interrupting.
  • Anchor the response to the point of intent. When the user acts on a specific element, the feedback should appear at or near that element, not in a distant corner (see interactions.md — Anchor Responses to the Point of Intent, and Direct Manipulation).
  • Form validation is inline feedback by another name — keep it next to its field (see forms.md).
// Feedback on the control itself — animate the icon swap (see animation-and-motion.md)
<button onClick={handleCopy} aria-label={copied ? "Copied" : "Copy"}>
  {copied ? <CheckIcon className="size-4" /> : <CopyIcon className="size-4" />}
</button>

Inline feedback is the right default for high-frequency actions. The more often the user does something, the less it should interrupt them (see ui/animation-and-motion.md — Micro-Interaction Priorities).

Banners

A banner is a persistent, page-level message that stays until the underlying condition resolves or the user dismisses it. It's for state, not for events.

  • Use for system or page-wide conditions: "You're offline," "Scheduled maintenance at 2am," "Your trial ends in 3 days," "Verify your email to continue."
  • Dismissible vs sticky: dismissible for informational banners the user can choose to ignore; sticky (non-dismissible) only when the condition genuinely blocks or gates functionality and dismissing would hide something the user must address.
  • Place it where it scopes correctly: a top-of-page banner for app-wide state, a section banner for one region's state. Don't put global state in a section, or vice versa.
  • Don't stack banners. Two banners competing at the top of the page is a sign the page has unresolved states piling up — coalesce or prioritize to one.
  • Carry severity with the same system as everything else (see Severity System below): an error banner looks different from an info banner, with icon and text, not color alone.

Badges & Counts

Badges are ambient, glanceable status — the quietest persistent channel. A dot says "something changed"; a number says "this many things."

  • Use a dot for binary "new/unseen" status where the exact count doesn't matter, and a number only when the count is actionable (12 unread, 3 pending approvals).
  • Use tabular-nums on counts that update in place so the badge doesn't reflow as the number changes (see ui/typography.md — Tabular Numbers).
  • Cap large counts ("99+") so the badge stays a fixed, small size and never blows out the layout.
  • Don't over-badge. If everything has a badge, nothing is notable. Badges should mark genuine, user-relevant change, not internal activity. A badge that's always lit is wallpaper.
  • A badge is a pointer, not the message — it tells the user where to look, and the destination (inbox, tab, list) carries the detail.

Blocking Alerts & Modals

A modal alert stops everything and demands a response. It's the heaviest channel and the most abused — reserve it for the rare message that genuinely must be acknowledged before the user continues.

  • Use only for must-acknowledge moments: irreversible or high-consequence confirmations (delete account, discard unsaved work, confirm a payment), or a decision the app literally cannot proceed without.
  • Make the safe choice prominent and the destructive one secondary. Don't auto-focus the destructive action; make Cancel the easy, obvious default (see interactions.md — Confirm Destructive Actions, and ui/hierarchy.md — destructive action hierarchy).
  • Prefer Undo over a confirm dialog wherever the action is reversible. Interrupting before a reversible action is more friction than letting it happen and offering Undo after (see Toasts above). Save the modal for the truly irreversible.
  • A modal is for blocking the user, so it must trap focus, lock background scroll, and return focus to the trigger on close. The full focus-management contract lives in interactions.md — Focus Management.

Notification Center / Inbox

An inbox is the durable channel for messages that arrive asynchronously and can be handled later — mentions, comments, completed background jobs, system updates. It's the opposite of a toast: nothing vanishes.

  • Track read/unread state and let the user clear it. A badge on the inbox entry point points the user to it (see Badges above).
  • Group or order by recency and relevance. A flat firehose is as useless as no inbox.
  • Make items actionable where possible — jump straight to the thing the notification is about, don't just describe it.
  • An inbox complements transient channels; it doesn't replace them. Something urgent still warrants a toast or banner now, and lives in the inbox for catch-up later.

Severity System

Info, success, warning, and error are a system, not four ad-hoc colors. Define them once and use them consistently across every channel — a warning toast, a warning banner, and a warning inline message should all read as the same severity.

  • Info — neutral, ambient. Lowest urgency.
  • Success — confirmation. A positive accent or check.
  • Warning — caution, reversible consequence.
  • Error — failure, needs attention (see errors.md).

Never carry severity by color alone. Pair each level with an icon and text so it survives color blindness, screen readers, and grayscale (see review-rules.md — Color-only information, and ui/color.md — Don't Rely on Color Alone). Color is the fastest signal but never the only one.

// Severity = color + icon + text, never color alone
const SEVERITY = {
  success: { icon: CheckIcon, label: "Success" },
  warning: { icon: AlertTriangleIcon, label: "Warning" },
  error: { icon: AlertCircleIcon, label: "Error" },
  info: { icon: InfoIcon, label: "Info" },
};

When you pick the colors, hand-pick accessible values rather than lightening with opacity, and keep them in oklch() like the rest of the system (see ui/color.md).

Store: Make the Ending Memorable

When completion feels uncertain or forgettable, design the final moment to leave a correct memory:

  • Clear feedback: state what happened and what changed.
  • Reassurance: confirm that the user is on the right path and explain what comes next.
  • Care: show that the product protects the user's outcome, time, and attention.
  • Useful delight: exceed an expectation with humanity that supports the task, not decoration alone.

The ending influences the next interaction. A clean completion can recover some earlier friction; an ambiguous finish can undermine an otherwise successful flow. Match the feedback channel to the weight of the event and give the user a real exit rather than manufacturing another open loop.

Timing & Frequency

Notification fatigue is a real failure mode. The fix is restraint and coalescing, not louder alerts.

  • Coalesce. Five files finished uploading is one notification ("5 files uploaded"), not five. Batch related events into a single message.
  • Debounce bursts. Rapid-fire changes (a collaborator typing, a counter ticking) should settle before notifying, not fire on every tick.
  • Respect frequency. A message the user sees constantly should get quieter over time, not repeat at full volume. The hundredth "saved" doesn't need a toast — an inline indicator suffices.
  • Don't notify for things the user just did and can see. If the result is already visible on screen, a notification confirming it is redundant noise.
  • Time it to the moment. Feedback for an action belongs at the action, immediately. A delayed toast that arrives after the user has moved on is confusing.

Sound

Audio is the fastest feedback channel (about 25ms versus ~250ms for visual processing), but it's also the most intrusive, so it's opt-in and sparing. Match the sound to the weight of the action — a subtle tick for a minor confirmation, a fuller tone for a significant success (see sound-design.md for the full system).

  • Sound complements, never replaces, visual feedback. A user with sound off must lose nothing.
  • Respect prefers-reduced-motion as a proxy for "minimize sensory feedback," and always provide an explicit toggle (see sound-design.md).
  • Reserve it for moments that earn it: a payment confirmation, a completed long-running job, a critical error. Don't sound every toast.

Accessibility

Sighted users catch a toast in their peripheral vision; screen-reader users hear nothing unless you announce it through a live region.

  • Use aria-live="polite" (or role="status") for non-urgent notifications — toasts, success confirmations, inline updates. Polite waits for a pause before announcing, so it doesn't interrupt.
  • Use aria-live="assertive" (or role="alert") only for urgent messages that must interrupt — critical errors, blocking warnings (see interactions.md — Accessibility Announcements).
  • Never steal focus for a non-blocking notification. A toast that grabs focus interrupts the keyboard user mid-task, contradicting its own "non-blocking" promise. Only blocking modals move focus (see interactions.md — Focus Management).
  • Give the user time to act on actionable notifications — don't auto-dismiss an Undo toast before a screen-reader user has heard it and reached the button.
  • Icon-only notification controls (a dismiss "×") need an aria-label (see review-rules.md — Icon-only buttons missing aria-labels).

Optimistic & Async Feedback

Most feedback is about acknowledging work in flight or done. Make the interface feel instant by updating optimistically and reconciling on the server's answer.

  • Update the UI the moment the action is likely to succeed, then reconcile when the server responds; on failure, roll back and tell the user, ideally with a retry or Undo (see interactions.md — Optimistic Updates).
  • Keep the cause visible during async work — a pending state on the clicked control is more trustworthy than a distant spinner (see interactions.md — Honest Loading States).
  • For background jobs the user navigated away from, the completion belongs in a toast (if they're still around) and the inbox (for catch-up) — not lost.

Checklist

Channel choice

  • Quietest channel that still delivers the message (inline < badge < toast < banner < inbox < modal)
  • No channel heavier than the message needs
  • Critical info never lives only in a transient toast

Toasts

  • Consistent position for every toast
  • Duration scales with reading time; actionable toasts last longer
  • Stack capped (~3–4); rest queued or collapsed
  • Destructive actions offer Undo instead of a pre-confirm where reversible
  • Toasts never steal focus

Banners & badges

  • Banners carry page-wide/system state, dismissible unless gating
  • No competing stacked banners
  • Badges mark genuine user-relevant change, not internal activity
  • Counts use tabular-nums and cap at "99+"

Modals

  • Reserved for must-acknowledge / irreversible decisions
  • Safe choice prominent; destructive action not auto-focused
  • Undo preferred over pre-confirm for reversible actions

Severity & timing

  • Info/success/warning/error defined once, used across all channels
  • Severity = color + icon + text, never color alone
  • Related events coalesced into one notification
  • No notification for results already visible on screen

Accessibility & feedback

  • aria-live="polite" for non-urgent, assertive for critical
  • Non-blocking notifications never move focus
  • Dismiss controls have aria-label
  • Optimistic updates roll back visibly on failure
  • Sound is opt-in, matched to action weight, never the only signal
  • Completion clearly states what changed, what comes next, and how to exit

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-ux-principle({ topic: "notifications" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems