Order saved. View orders
Overview
A banner sits inside the page (or pinned to its top) and reports something the user needs to know — an order saved, a payment declined, a setting that needs verification. It carries a tone (what kind of news), a type (how much copy), and a position (where on the page). The body of the page keeps working; the banner just informs.
Banner is for page-scope news
Reach for a banner when the message applies to the whole surface (the form, the order, the settings page). For field-specific errors, use the inline messagebelow the input. For transient confirmations that don't block anything, a toast is friendlier — it appears, announces, and disappears on its own.
Anatomy
Five parts. The container takes the tone-tinted background and tone-tinted text. Inside it, a 24 px leading icon names the tone, a message body carries the copy, and an optional 48 × 48 dismiss button sits trailing.
Order saved. View orders
Container
Tinted background drawn from
background.accent.<hue>.subtlestper tone. 56 px minimum height. 4 px rounded corners on embedded; flat on page-top.Tone icon
24 px Material Outlined glyph in the tone's text colour. check (success), warning_amber (caution + warning), error_outline (critical), info (info). The icon doubles up the tone — never replaces it.
Message
Inter Tight Medium 16/20 in the tone's text colour. Single-line types pair the message with an optional inline action; multi-line types stack a heading + bullet list.
Inline action (optional)
Underlined link in the tone's text colour. Sits at the end of the message — “View orders”, “Resolve”, “Learn more”. Single action only; for two or more, drop the link and use a separate button row.
Dismiss (optional)
48 × 48 ghost button with a 24 px close glyph. Drop entirely when the banner is blocking — the user must act on it.
Tones
Five tones, each tied to a specific kind of news. Pick the tone the page is announcing — never the tone you wish the page were announcing. A successful action that feels precarious is still success.
Order saved. View orders
Order needs review. View orders
Order needs attention. View orders
Order cancelled. View orders
Select a table to move. View orders
Caution and warning use the same icon
Both reach for warning_amber. The difference is colour — caution uses yellow (a soft hint), warning uses orange (something needs attention). Never use red for either; red is reserved for critical (the action stopped, something failed).
Types
Two layouts cover almost every banner in the product. Single-line for status updates and confirmations; multi-line for validation summaries that list multiple problems.
Single line
Order cancelled. View orders
One line of copy, an optional inline action. Use when the message fits in a sentence — confirmations, simple status changes, dismissible tips.
Multi line · bullets
Errors found
- Settlement account is missing a routing number.
- Beneficial owner address is incomplete.
- VAT identifier failed validation.
Heading + bullet list. Use when the user needs to act on multiple items inside the same surface — validation summaries, compliance warnings, change logs.
Position
Two positions. Page-top spans the viewport and sits above the page content — use it when the message is about the whole page or session. Embedded sits inside a section, scoped to its surrounding context.
Select a table to move. View tables
Embedded
Sits inside a section of the page. 4 px rounded corners. Width matches the surrounding column.
Order needs review. Review
Dismiss
The dismiss action lets the user clear the banner manually. Default-on for confirmations and non-blocking notices; default-off for blocking ones — the user must resolve the underlying issue, not dismiss the message.
Dismissable
Order saved. View orders
The default. Trailing 48 × 48 ghost button with a 24 px close glyph. Use for transient confirmations and non-blocking warnings.
Non-dismissable
Settlement account needs verification. Resolve
Drop the dismiss when the message is blocking — the user must act on it before the page can do its job. Critical compliance, missing required data, paused-account warnings.
Behavior
- Banners persist until dismissed or resolved. Unlike a toast, they stay visible. The user closes them explicitly (dismissable) or the underlying state changes (validation passes, settings update). Don't auto-fade.
- One banner per scope. At most one banner per page-top, one per embedded section. Stacking banners reads as noise — the user stops reading after the second. If the page genuinely has multiple problems, fold them into a single multi-line banner.
- The inline action is one verb. “View orders”, “Resolve”, “Learn more”. If you need two paths, the banner isn't the right surface — reach for a dialog or a sheet.
- Dismissed banners don't come back on the same session. When the user dismisses a banner, persist that dismissal for the session (or the user, when authenticated). Re-rendering the same banner on a route change feels broken.
- Banners enter and exit, they don't pop. 200 ms fade + a small
translateYon entry. Honourprefers-reduced-motion— keep the fade, drop the translate.
Banner vs. inline message vs. toast
Three feedback surfaces share the same tone palette but earn different jobs.
Banner
Page-scope. Persistent. Tinted bar with a leading tone icon and an optional dismiss. Use for status that applies to the whole surface — order saved, account paused, validation summary at the top of a form.
Inline message
Field-scope. Persistent. Single-line helper or error directly below the input. Use for feedback specific to one form field — “Phone must include country code”.
Toast
Session-scope. Transient. Floats over the page, auto-fades after a few seconds. Use for confirmations the user can acknowledge without acting — “Saved.”, “Copied to clipboard.”
Accessibility
- Role per tone.
role="alert"for critical and warning — interrupts current screen-reader output.role="status"for success, caution, and info — announces politely without interrupting. Don't applyrole="alert"to a confirmation; it interrupts and feels rude. - Tone never carries meaning alone. Every banner pairs the colour with an icon and a message. Screen-reader users and people with colour-vision differences both read the message from the icon + copy.
- Inline action is a button or a link. When it navigates, use
<a>; when it performs an action,<button>. Don't style a<span>withonClickto look like a link. - Dismiss button is labelled.A bare close glyph isn't enough —
aria-label="Dismiss"(or a more specific verb when the action does more than dismiss the banner). - Focus management: page-top banners with
role="alert"don't steal focus. Embedded banners that announce a validation failure can move focus to the first invalid field, not to the banner itself. - Contrast: every text colour ships at WCAG AA against its tone background. The 900 step of each hue is the minimum; never lighten for aesthetic reasons.
Code
One Banner component handles every variant. The tone is the only required prop — type, position, and dismiss have sensible defaults.
tsx
import { Banner } from "@flatpay-dk/ui";
// Default — single-line, embedded, dismissable
<Banner tone="success" action={<a href="/orders">View orders</a>}>
Order saved.
</Banner>
// Critical — multi-line bullets, non-dismissable
<Banner
tone="critical"
type="multiLine"
position="embedded"
title="Errors found"
bullets={[
"Settlement account is missing a routing number.",
"Beneficial owner address is incomplete.",
]}
dismissable={false}
/>
// Page-top info banner with a single inline action
<Banner
tone="info"
position="pageTop"
action={<a href="/tables">View tables</a>}
>
Select a table to move.
</Banner>
// Caution with no action — the message itself is the news
<Banner tone="caution" dismissable>
Order needs review before it can ship.
</Banner>
// Persisting dismissal
const [dismissed, setDismissed] = useLocalStorage("banner.kyc", false);
{!dismissed && (
<Banner
tone="warning"
onDismiss={() => setDismissed(true)}
action={<a href="/kyc">Verify</a>}
>
Settlement account needs verification.
</Banner>
)}Best practices
Banners are read fast. The defaults below keep them legible without shouting.
Order saved. View orders
Do
Lead with the news. 'Order saved' as the first words; the action follows. The user knows what happened in one glance.
We're delighted to inform you that your order has been saved successfully.
Don't
Don't bury the news in a sentence. 'We're delighted to inform you that…' is marketing voice; banners are operator voice.
Settlement account needs verification. Resolve
Do
Drop the dismiss for blocking warnings. The user has to resolve the underlying issue — closing the banner doesn't help.
Settlement account needs verification. Resolve
Don't
Don't make a critical message dismissable. The user closes it, the issue stays — and the next page load makes it look like nothing was wrong.
Errors found
- Settlement account is missing a routing number.
- Beneficial owner address is incomplete.
Do
Fold multiple problems into one multi-line banner. The user reads one banner and gets a punch list.
Settlement account incomplete.
Beneficial owner missing.
VAT identifier failed.
Don't
Don't stack three single-line banners. Two is already noisy; three is a wall the user stops reading.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| tone | "success" | "caution" | "warning" | "critical" | "info" | "info" | What kind of news the banner is announcing. Drives background, text, icon, and ARIA role. |
| type | "singleLine" | "multiLine" | "singleLine" | Single-line for one sentence + optional inline action. Multi-line for a heading + bullet list of details. |
| position | "pageTop" | "embedded" | "embedded" | Page-top spans the full viewport, no rounded corners. Embedded sits inside a section with 4 px corners. |
| title | string | — | Heading rendered above the bullet list when type="multiLine". Inter Tight Medium 16/20. |
| bullets | string[] | — | Bullet list rendered when type="multiLine". Each item carries the tone's text colour. |
| children | ReactNode | — | Single-line message body. Used when type="singleLine". |
| action | ReactNode | — | Inline action — typically an anchor or button rendered as an underlined link at the end of the message. |
| dismissable | boolean | true | Renders the trailing 48 × 48 close button. Drop for blocking messages the user must resolve. |
| onDismiss | () => void | — | Fires when the dismiss button is clicked. Persist the dismissal so the banner doesn't return on the next render. |