Banner, toast & empty state
Three messages about a view rather than about a subject in it — a banner that stays, a toast that confirms and leaves, and an empty state that says what would fill the space.
Verdict banner
Keep the shed under 10 m² and behind the building line and you may start today.
A standard council check against the written rulebook. Tick every box and it must be approved; neighbours can't formally object.
Code
<GdVerdictBanner
title="Building + council"
:required="['building', 'council']"
decided-by="Riverton City Council"
time="6–12 weeks"
>
A standard council check against the written rulebook. Tick every box
and it must be approved; neighbours can't formally object.
</GdVerdictBanner>Toasts
Code
<GdToast action="View" @action="openProject">
Parcel saved to Jarrah St project
</GdToast>The system
The component above is only the drawn box. The SYSTEM is useGdNotify() plus GdToastHost: the app mounts the host once at the end of its shell, and from then on anything — a composable, a poll, a catch block — calls notify() and the notice appears bottom-centre, above everything, owned by no panel. That last part is the point: the notifications that matter most are about work that outlives the surface that started it, and a toast a panel mounts dies with the panel.
Code
// once, at the end of the app shell
<GdToastHost />
// anywhere at all
const { notify } = useGdNotify()
notify("Parcel saved to Jarrah St project", { action: "View", onAction: openProject })
notify("Zone clip failed", { tone: "danger", action: "Retry", onAction: retry })Empty states
No saved parcels yet
Search for an address and save it to a project. Saved parcels keep their scenarios and re-check themselves when the scheme changes.
Code
<GdEmptyState
title="No saved parcels yet"
icon="M9 20H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-5"
>
Search for an address and save it to a project.
<template #action>
<GdButton variant="primary" size="sm">Search an address</GdButton>
</template>
</GdEmptyState>API
GdVerdictBanner
Props
titlerequiredstring—The plain-English verdict — "Building + council". `stackLabel()` produces exactly this string from a set of authorities.
requiredreadonly ApprovalAuthority[]—The authorities, rendered as an icons-only stack beside the title. Omitted or empty, the stack reads "No approval needed".
decidedBystring—Who decides — "a private certifier", "Riverton City Council". Renders in the footer strip.
timestring—The typical duration — "2–4 weeks". The second of the two facts a person plans around.
Slots
defaultOne paragraph saying what the verdict means in practice. The footer strip only appears when `decidedBy` or `time` is set.
GdBanner
Props
tone"ink" | "accent""ink"The ground. `ink` is the attention-getting near-black surface; `accent` is the softer product message. Never a status hue.
dot"success" | "info" | "caution" | "danger"—A 7px status dot on the left — the condition's severity, not the banner's.
dismissibleboolean—Adds the close button. It emits `dismiss` and nothing else — the banner does not hide itself.
Slots
defaultThe message. A `<strong>` lead sentence then the detail; the banner styles that emphasis for its own ground.
actionOne button on the right.
Events
dismissThe close button was pressed. Hiding it, and remembering that, is the caller's.
GdToast
Props
tone"success" | "info" | "caution" | "danger""success"Picks the glyph and its on-dark colour. `danger` renders role="alert"; the other three render role="status".
actionstring—The label of the single undo-or-view affordance. One only.
dismissibleboolean—Show a close affordance and emit `dismiss`. The host sets this on sticky notices; a close button on a five-second toast is an invitation to chase it.
Slots
defaultThe confirmation, in one line.
Events
actionThe action link was pressed.
GdToastHost · useGdNotify
The host takes no props — the queue is the API. notify(message, options?) returns an id; options is { tone, action, onAction, sticky }, with sticky defaulted from the tone. dismiss(id) removes a notice early. Client-side calls only: a notification raised during server render has no one to see it, and callbacks cannot cross the SSR payload.
GdEmptyState
Props
titlerequiredstring—What is not here. "No saved parcels yet", not "No data".
iconstring—A 24×24 SVG PATH, not a registry name — drawn in a 64px tile, the one place a tile gets real size and a shadow. An icon name here silently renders an empty tile; use the icon slot instead.
Slots
defaultThe body: what would put something here.
iconThe glyph, for an app with its own icon set. Use this instead of the icon prop — anything but a path needs a component.
actionThe button that does it. Leave it out for a search that found nothing.
Usage Guidelines
- Use a banner for a condition of the whole VIEW that stays until it changes — a scheme amendment, a quota, an outage.
- Mount
GdToastHostonce, in the shell. A host per panel is a toast that dies with the panel — which is backwards, because the notices that matter most outlive the surface that raised them. - Use a toast only to confirm something the user just asked for. Anything they must read to avoid harm is an alert or a dialog.
- Use an alert when the message is about the DATA rather than the view, and belongs beside the thing it is about.
- Use
tone="ink"when the banner must be noticed, andaccentfor a product message that can wait. Put severity ondot, never on the ground. - Handle
@dismissyourself and persist the answer where the preference belongs — the banner will not hide itself. - Give an empty state a body and an action, except after a search that found nothing: the user already knows what to do.
- Pass the empty state's glyph as a path, or use
#icon—iconis adattribute, so a name goes in as<path d="folder">and draws nothing. It is valid SVG, so nothing errors; the tile just comes out blank. - Reach for the verdict banner only for the planning answer itself. It is one component with one job, not a general result panel.
Behavior & Anatomy
The verdict banner
It never animates its wording: an icon joins or leaves the stack the instant a threshold is crossed, so a changing answer reads as a change of state rather than as a loading state.
Page banners
Dismissal is the caller's to persist. A banner that reappears on every route change has not been dismissed, it has been ignored — and the component cannot know where that preference belongs.
Toasts
The surface is dark in BOTH themes — a toast is not a card, and making it dark is how it reads as transient chrome rather than as content that has appeared in the page.
Empty states
An empty state with only a title is a dead end. The body says what would put something here, and the action does it. The one case where an action is wrong is a search that found nothing: the user already knows what to do, and a button that says "Search again" is noise.