HabBanner
A persistent, in-flow notice about the page or a form. Flat, no elevation — it belongs to the layout.
<HabBanner tone="warning" title="Sync paused">
Retrying in 30 seconds.
</HabBanner>Which notice
The three differ by placement, not by look. They share one anatomy — tone edge, full-strength tone icon, tinted surface, neutral copy:
| Component | Is |
|---|---|
HabBanner | in-flow and persistent |
HabStatusStrip | the standing state of a region — thicker edge, heavier title |
HabToastHost | elevated, fixed and transient |
The edge width is the difference you can see. A banner takes the 4px --hab-edge-notice: something has happened and will stop being true. A strip takes the 8px --hab-edge-classify: this is the standing condition of the region. Using the wrong one makes the width mean two things.
Never use a banner for a decision that must be made before continuing — that is a modal.
Solid is the escalation
Saturation is the escalation axis, not the component. solid is reserved for a blocking or brand-level announcement, at most one per screen. It drops the edge, because the fill already carries the tone.
Recession on a solid fill is a mix toward the fill, never opacity — opacity is the alpha-text problem the solid ink ramp exists to avoid, and it compounds over a tint. Note warning takes ink on its fill rather than white, so a white wash would disappear on it; every recession and the progress track are mixed per tone for exactly that reason.
Progress and dismissal
<HabBanner title="Importing hosts" :progress="40" @dismiss="hide">
412 of 1030 rows processed.
</HabBanner>progress renders a determinate bar with its value reported — unlike HabProgressBar, which is indeterminate by omitting the value. It borrows the banner's title as its accessible name; without a title, pass progress-label, and Habitus warns if you pass neither.
The dismiss affordance appears only when a @dismiss listener is attached. A banner the user cannot dismiss should not have a close button, so the listener is what decides.
An icon named at runtime
icon takes SVG path data, which cannot travel through JSON. Where a glyph's identity arrives from an API response, a database row, or a field a user typed into, pass your own markup through the leading slot instead — it replaces the tone glyph and keeps the tone's color. There is no name list Habitus could ship that would cover an open set.
<HabBanner tone="error" :title="notice.title">
<template #leading>
<i :class="`mdi mdi-${notice.icon}`" />
</template>
{{ notice.message }}
</HabBanner>That example uses the @mdi/font webfont, which resolves any of the 7,447 MDI names from a class name for the price of one stylesheet. Loading it is the host application's call; Habitus does not.
Every component with an icon prop has the same escape hatch, and this section is the one place it is explained — the per-component API tables name the slot without repeating the reasoning. It is leading everywhere except HabIconTile, which takes default: that component is nothing but the glyph, so there is nothing for an adornment to lead. HabInput also has trailing, which stays inside its button so the label and click handler survive the swap.
The quiet register
<HabBanner tone="neutral" edge="none" :icon="mdiLockOutline">
Your role can read this process. Attaching activities needs write access.
</HabBanner>tone="neutral" with edge="none" is the bar for content where nothing is wrong — a read-only strip, a "this is how this screen works" line. Banner's 4px notice edge says something has happened; HabStatusStrip's 8px edge says this is the standing condition of the region. Both overstate a sentence that is merely true, which is why every read-only surface in the product was inventing its own bar.
It ships no default glyph. The other four tones each have one that names a state, and there is no glyph that means "no state" — defaulting to the info mark would put a claim on the one bar whose job is not to make one. Pass the glyph that says what this bar is about: a lock, a clock, an eye.
edge is orthogonal to tone, so any tone can drop its edge. neutral has no tone color to draw one with, so its left border stays the shell's own hairline either way.
Accessibility
- Give it a
title, a body, or both. The tone's color and glyph repeat the meaning; they must not be the only carrier of it. - The icon is decorative and
aria-hidden. Pass:icon="null"to drop it where the title already says everything. - Markup you pass through
leadingis yours to hide. Habitus setsaria-hiddenon the glyph it renders, but not on the slot: the same slot is where aHabAvataror aHabCheckboxgoes, and hiding those would be wrong. So a decorative glyph of your own needs its ownaria-hidden="true". - The dismiss button carries
aria-label, fromdismiss-label. There is no default — the string is yours to supply, and an@dismisshandler without it warns. - A banner that appears in response to something is not announced by default. This renders static markup; if it arrives asynchronously, put it in a live region, or use a toast, which is built for exactly that.
- Every tone's tint/text and fill/ink pair is contrast-measured. The mixes for recession are derived from those, so they inherit the measurement.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
dismissLabel | string | undefined | Accessible name for the dismiss button. Required on any banner with an @dismiss handler: Habitus ships no default, so the string is the host application's to translate. |
edge | "notice" | "none" | "notice" | Which left edge the banner draws, if any. |
icon | string | null | undefined | SVG path data, e.g. mdiAlert from @mdi/js. Defaults per tone; pass null to drop it. |
note | string | undefined | Small right-aligned line — a timestamp, or an AI-output disclaimer. |
progress | number | undefined | 0–100. Renders a determinate progress bar, for a countdown or a job the banner is reporting on. |
progressLabel | string | undefined | Accessible name for the progress bar, when there is no title to borrow. A bar that reports a value with no indication of what it measures is announced as a bare number. |
title | string | undefined | — |
tone | "error" | "neutral" | "info" | "success" | "warning" | "info" | neutral is the quiet register: no tint, no tone glyph, and nothing claiming anything is wrong. Pair it with edge="none" for a read-only or how-this-works strip. It ships no default icon — there is no glyph that means "neutral", so pass the one that means this (a lock, a clock). |
variant | "tinted" | "solid" | "tinted" | — |
Slots
| Slot | Bindings | Description |
|---|---|---|
action | any | One trailing affordance — a text button or a link. |
default | any | Body copy. |
leading | any | Leading adornment, replacing the glyph. Takes precedence over icon. |