banner
A banner tells people something about the content of the page, such as titles that can't be checked, in one or two plain sentences and with a way forward. The technical part waits behind “details”.
when to use
- For a condition of the page's content that lasts until it is resolved or dismissed: “105 titles can't be checked: the catalog holds them without an identity.”
- For an error from loading or from an action, with a way forward: “Couldn't load the list.” and “try again”.
- Above the content it is about. Show one or two at a time at most: with more, none of them reads as important.
when to use something else
- To confirm an action someone just took. For “8 episodes queued”, use a toast: it says so and leaves.
- For one item. A field's error goes in the error text of its field, and a row's state in a status in the row.
- In place of a list that has nothing to show. When the list is empty, or couldn't load and there is nothing older to show, use an empty state where the list would be. Keep the banner for content that is still on the screen.
- To stop everything for a decision. Use a modal.
- For help that is always true about one control. Put it in the hint of the control's field.
anatomy
- Banner. A square box, tinted and framed in shades of the state's colour, with a thicker bar in the colour itself down its left edge.
- Glyph. The state's small symbol, in its colour, beside the first line of text. Assistive technology skips it.
- Text. One or two sentences, in the body face. It takes the free width.
- Actions (optional). Small buttons after the text. When there isn't room beside it, they move under it.
- Details toggle (when there are details). A quiet text button, “details”, with a small arrow that turns down while the details are open.
- Details (optional). The technical part, under the text, in monospace, set off by a thin line on its left. Line breaks are kept, and long ids break anywhere.
- Dismiss (optional). A small square button with a cross, at the right edge.
variants and states
There are five states: neutral, information, success, warning and error. Information is the default. Each has its own colour and its own glyph.
A screen reader announces each state in its own way: politely, at its next pause, or at once, interrupting what it is reading.
| state | what a screen reader says | use it for |
|---|---|---|
| Neutral | The text alone, politely | A plain fact about the content: what runs on its own, what is switched off |
| Information | “information:” and the text, politely | Under way, or worth knowing: the list is being refreshed, an index is rebuilding |
| Success | “success:” and the text, politely | A good condition that lasts, such as every source answered in the last hour. Or what a save changed on this page: “Saved. Times on this page now show in UTC.” A plain confirmation is a toast |
| Warning | “warning:” and the text, politely | It works, but someone should look: titles that can't be checked, sources over their limit, a refresh that failed while the last data still shows |
| Error | “error:” and the text, at once | Failed or blocked, and needs action: an action that failed, a list that couldn't load. Give it a way forward |
details
The details keep the technical part, such as ids, raw errors and HTTP codes, behind a disclosure, in monospace. They stay closed until someone opens them, and an announcement never reads them out.
dismiss
A banner can carry a dismiss button. Offer it for a message people can read and move on from, such as what a save changed, or an action that failed. Leave it off a condition that is still true, like a list that can't load.
The banner doesn't remember that it was dismissed: the screen does, and brings the banner back when its condition changes again.
behaviour
- The banner's controls take focus in this order: its actions, then the details toggle, then dismiss. The text takes no focus.
- enter or space on the details toggle shows or hides the details.
- esc does nothing. A banner is part of the page; it doesn't float above it, as a dialog does.
- The banner announces its text when it appears or changes. Accessibility, below, says what that means for each state.
The full keyboard map is on the banner's React page.
accessibility
- Only the text is read out. The text is a live region. An update reads the whole message, and never the actions, the details or dismiss.
- Errors interrupt. An error is announced at once; every other state politely, when the screen reader has a pause. A banner that is on the page from the start and doesn't change can be a still note instead, which isn't announced at all: it is then read with the page, like any other text.
- The state word. Every state but neutral reads its name before the text, from words that are hidden on screen: “warning: 105 titles can't be checked”. The word can be translated, given to neutral as well, or left out. A status names only warning and error.
- Not by colour alone. The glyph is hidden from assistive technology, and each state has a glyph of its own shape, so the state never rests on colour alone.
- Closed details are neither read out nor reachable with the keyboard.
- Dismiss is named “dismiss”, and it is larger than the minimum target size. The details toggle and dismiss show the same focus outline as every control; interaction explains it.
Today:
- A live region announces changes to text it already holds. So a banner that is announced politely, and appears together with its text, may go unannounced in some screen readers. A banner that interrupts, as an error does, is announced as it appears.
- Dismissing removes the button that had focus, and focus falls back to the page. Move it yourself, to the content's heading or to the next control.
- The details toggle and dismiss have the same names on every banner. With two banners on a page, name them so that people can tell them apart.
content
- The text. One or two plain sentences: what is true, then what it means or what happens next. “105 titles can't be checked: the catalog holds them without an identity.” It is in the body face, the reader's language, so it is written in sentence case. No ids, codes or raw errors.
- Numbers. Exact, with a thousands separator: “2,500”, not “2.5k”.
- An error says what failed, and offers the way forward: “Couldn't load the list.” and “try again”. When the last data still shows, keep it, and make the banner a warning that says how old it is: “Couldn't refresh the list; it shows what was loaded 4m ago.”
- Actions. One or two small quiet buttons, each a verb, in the lowercase monospace voice: “show them”, “try again”, “ask the catalog again”. Not the emphasized button: the view's one emphasized action is elsewhere.
- Details. The raw part, as it came: “GET /api/missing: http 502 bad gateway”, “request id 7d1c0e4a”. Line breaks are kept.
do and don't
related
- Message handling: which kind of message goes where, from a banner or a toast to inline text, details or a modal.
- Toast: the confirmation of an action that just finished.
- Empty state: in place of a list that is empty or couldn't load.
- Status: the state of one item, in its row.
specs
| state | in code | announced |
|---|---|---|
| Neutral | state="neutral" |
Politely (role="status"), with no state word |
| Information | state="information" (the default) |
Politely, after “information:” |
| Success | state="success" |
Politely, after “success:” |
| Warning | state="warning" |
Politely, after “warning:” |
| Error | state="error" |
At once (role="alert"), after “error:” |
Each state sets three colours: --state-<state> for the bar on the left and the glyph, --state-<state>-border for the frame, the details rule and the dismiss button's frame on hover, and --state-<state>-bg for the fill. The banner carries data-state.
| part | spec |
|---|---|
| Banner | A row: the glyph, then the text with its actions and details, then dismiss. Padding 12px, 16px on the left. A 3px rule on the left in --state-*, a 1px --state-*-border frame, a --state-*-bg fill, square corners. Inter 13px, line-height 1.5, --fg. The glyph and the text 12px apart. |
| Glyph | The state's 14px glyph in --state-*, set 3px down so it sits on the first line. aria-hidden. icon: true (the default), false to hide it, or an element of your own. |
| Text | children. It takes the free width, at least 24ch before the actions wrap under it; 16px from the actions beside it, 4px above them once they wrap. |
| Actions | actions. 8px apart, wrapping under the text when there is no room. One or two buttons, size="sm" and variant="ghost", never variant="primary". |
| Details toggle | Shown when details is set, named by detailsLabel (default “details”). A quiet text button: JetBrains Mono 12px, --fg-muted (--fg on hover), lowercase, 28px tall, padding 0 8px. A 10px caret that turns 90° to point down while the details are open, in 120ms ease, with no movement under reduced motion. aria-expanded and aria-controls. |
| Details | details; defaultDetailsOpen opens them on the first render. 8px under the text. JetBrains Mono 12px, line-height 1.55, --fg-muted, padding 8px 12px, a 1px --state-*-border rule on the left. Line breaks kept (white-space: pre-wrap), long IDs broken anywhere (overflow-wrap: anywhere). hidden while closed, and outside the live region. |
| Dismiss | Shown when onDismiss is set, named by dismissLabel (default “dismiss”). A 28px square at the right edge (above the 24px minimum target size), with a 12px × in --fg-muted; on hover, --fg and a --state-*-border frame. The banner doesn't hide itself: onDismiss is called, and you stop rendering it. |
| Focus | The details toggle and dismiss: the 2px --focus-ring outline, 1px outside, on keyboard focus only. |
| setting | in code | what it does |
|---|---|---|
| Announcing | live: 'polite', 'assertive' or 'off' |
Polite gives the text role="status", assertive role="alert", off no role at all: a still note, read with the page. The default is assertive for error and polite for the rest. The text is aria-atomic, so an update reads the whole message. |
| State word | stateLabel |
Visually hidden text before the message: the state's name, for every state but neutral. A string replaces the word (and gives neutral one); false leaves it out. |