# Desktop UI Feedback Messaging

> Use whenever designing or reviewing toasts, banners, inline errors, alert dialogs, empty states, or loading states in a desktop app — anywhere the app communicates status, success, failure, or "nothing here yet" to the user. Depends on desktop-ui-foundations and desktop-ui-color-system. Trigger on "error message," "toast," "banner," "empty state," "loading state," or "notification."

- Skill: `simply-ehis/desktop-ui-feedback-messaging` (Agent Skill)
- Install (CLI): `npx skillmds@latest add simply-ehis/desktop-ui-feedback-messaging`
- Raw SKILL.md: https://api.skillmd.com/api/skills/simply-ehis/desktop-ui-feedback-messaging/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: simply-ehis (https://skillmd.com/u/simply-ehis)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/simply-ehis/desktop-ui-feedback-messaging

---


# Feedback, Errors & Messaging (Desktop)

## Pick the right vehicle for the message

Using the same treatment (usually a toast) for everything is a common shortcut that stops working once messages vary in importance. Match the vehicle to the stakes:

| Vehicle | Use for | Persistence |
|---|---|---|
| **Inline field error** | A specific input is invalid | Stays until fixed |
| **Toast** | Confirmation of a completed background action ("File saved", "Export complete") — low-stakes, doesn't need acknowledgment | Auto-dismisses after 3–5s, pauses its timer if the window loses focus or the user hovers it |
| **Banner** | App- or view-wide status that persists until resolved (offline mode, an update available, a sync conflict) | Stays until dismissed or resolved — never auto-disappears on its own timer, since the condition it describes hasn't gone away |
| **Alert / confirmation dialog** | Something that blocks progress and needs an explicit decision (destructive confirmation, an unrecoverable error) | Stays until the user responds — see `desktop-ui-surfaces-overlays` for modal rules |

A background success (file saved) that pops a blocking dialog is over-escalated; a data-loss risk that only shows a toast that vanishes in 4 seconds is under-escalated. Match the vehicle to how much the user needs to actually register and act on the message.

## Severity hierarchy

Use a consistent 3–4 level severity scale, color-coded per `desktop-ui-color-system`, everywhere in the app:

`info` (neutral, accent-tinted) → `success` (confirmation) → `warning` (needs attention, not urgent) → `danger/error` (something failed or is at risk)

Never invent a one-off color for a message that doesn't map to one of these — consistency here is what lets users learn to gauge severity at a glance instead of reading every message fully.

## Writing the message itself

- **State what happened, in plain language** — not an error code or stack trace as the primary text (a code can go in an expandable "details" section for support purposes).
- **State what the user can do about it**, if anything — "Couldn't connect to the sync server. Retrying automatically." is more useful than "Sync failed."
- **Avoid blaming language and avoid over-apologizing** — "Something went wrong" with no other information is the least useful version of an error message; so is a message that over-explains with three sentences of preamble before the actual point.

## Banners specifically

- Position consistently — typically full-width at the top of the content area (below the header, not on top of it) so it doesn't cover navigation.
- Dismissible banners need a visible close control; banners describing an ongoing blocking condition (offline, license expired) can omit the close control since dismissing them shouldn't make the condition invisible.
- Don't stack multiple banners — if more than one condition is active, either combine them into one banner with the highest-severity styling, or queue them one at a time.

## Empty states

Every view that *can* be empty needs a designed empty state — not a blank pane, not just the loading spinner left on screen forever. A good empty state:

- Names what would normally appear here.
- Gives the primary action to fill it (a button, not just descriptive text), if there is one.
- Uses a small icon or simple graphic tied to the app's own visual language — not a generic stock illustration.

This is one of the fastest tells of a UI that was only ever tested with the happy path (see the anti-slop checklist in foundations) — design it deliberately rather than letting it default to nothing.

## Loading states

- **Skeleton screens** (shaped placeholders matching the eventual layout) for content that takes more than ~300–500ms to load — this reduces perceived wait time more than a spinner does, because the layout doesn't jump once content arrives.
- **Spinners** for short, indeterminate waits where a skeleton isn't practical (a button's own loading state, a small async action).
- **Progress bars** only when real progress can be measured (file transfer, export) — a progress bar that isn't tied to real progress and just animates indefinitely is worse than an honest spinner.
- Never leave a view with no loading indicator at all during a fetch — a frozen-looking blank pane reads as broken, not fast.

## OS-level (system) notifications

Different concern from in-app toasts/banners — these fire even when the app is minimized or in the background, via the OS notification center:

- Reserve them for things the user actually needs to know about away from the app — a long export finishing, a background sync error, an incoming message. Don't mirror every in-app toast out to the OS layer; that trains the user to ignore your notifications entirely.
- Ask for notification permission at the point it's first needed (right before the first thing that would use it), not on first launch before the user has any context for why the app wants it.
- Batch related notifications rather than firing one per item (one "12 files synced" notification, not twelve individual ones).
- Make them actionable where the OS supports it (a "View" or "Retry" action button on the notification itself) so clicking it takes the user directly to the relevant place in the app, not just to the app's default window.
- Respect OS-level Do Not Disturb / focus modes — don't attempt to bypass them.

## Accessibility & internationalization

- Toasts and inline status messages need to reach screen reader users without stealing focus — announce them through a live region (assertive for errors needing immediate attention, polite for routine confirmations), not just a visual fade-in.
- Error text must be real, specific text, never color or an icon alone, and it should be programmatically tied to the field or action it refers to, not just placed visually nearby (see `desktop-ui-accessibility-i18n`).
- Leave width headroom in banners and toasts for translated strings, which typically run longer than the English source.

## Review format

| Before | After | Why |
|---|---|---|
| A destructive-action failure shown as a toast that vanishes in 4s | Shown as a persistent banner or alert dialog requiring acknowledgment | The message needs to actually be registered, not just flash by |
| Empty list view is just a blank white pane | Icon + "No projects yet" + a "Create your first project" button | Undesigned empty states are one of the clearest signs only the happy path was built |
| Error text: "Error: 0x8007042" | "Couldn't save your file — the disk might be full. [Details]" | Users need plain language and, where possible, a next step; codes belong in expandable detail, not the headline |

