Banner
Banner: a borderless status note — a flat wash of the tone at 7–8%, a tone-coloured icon, and body copy inked toward the hue instead of muted grey. Four tones (info, warning, success, danger), each driven by three custom properties. No runtime dependencies.
Live preview
Installation
pnpm dlx shadcn@latest add @sc1m/bannerThis also merges the token layer and installs
src/lib/cn.ts. For the manual route — prerequisites,
components.json, and app-level setup — see Installation.
Usage
import { Banner } from "@/components/ui/banner";Composition
One element, no parts. The icon and the screen-reader prefix are chosen by
tone, so a banner is a tone and a sentence.
Banner├── (icon — chosen by tone, aria-hidden)└── childrenChildren land in a div, not a p. MDX wraps multi-line JSX children in a
paragraph of its own, and a <p> inside a <p> is unwrappable HTML — the
subtree fragments and hydration fails. This is why prose, lists, and code all
compose here without care.
Tones
Four tones. The tint is the signal, so the tone is legible before a word is read.
<Banner tone="info">Generated against 0.0.9 — regenerate before citing it.</Banner>
<Banner tone="warning">Two props here are deprecated and removed in 0.2.</Banner>
<Banner tone="success">All 41 registry items validated.</Banner>
<Banner tone="danger">Three variants have changed since this was written.</Banner>Example
The preview above, with its source. This is the demo module verbatim, so the code and the thing it renders cannot drift apart.
<div className="flex w-[560px] flex-col gap-2.5">
<Banner tone="info">
Hover and focus can’t come from a real pointer here, so those two
columns are forced with the same utility the component uses.
</Banner>
<Banner tone="warning">
Two props on this page are deprecated and removed in 0.2. The table
still documents them.
</Banner>
<Banner tone="success">
The registry rebuilt cleanly and all 41 items validated.
</Banner>
<Banner tone="danger">
This table was generated against 0.0.9. Three variants have changed
since — regenerate before citing it.
</Banner>
</div>API reference
Banner forwards every prop of a div.
alert for danger, status otherwise.Accessibility
- The banner is a live region:
role="status"(polite) forinfo,warningandsuccess, androle="alert"(assertive) fordanger. Only an error earns the right to interrupt what a screen reader is already saying. - Colour is never the only carrier of tone. The icon is
aria-hidden, so each tone contributes a visually hidden prefix instead — "Warning: ", "Error: " — and the tone survives greyscale, and anyone who cannot separate the hues. - Body copy is inked toward the tone rather than set in muted grey, which raises contrast against the wash instead of lowering it.
- Pass
roleexplicitly if a banner is rendered on page load and should not be announced at all.
Design tokens
The component reads semantic tokens only — no raw colour, radius, or duration appears in its source. Override the token, not the component.
Every tone is three custom properties: --banner-tone (the hue),
--banner-wash (how much lands in the fill) and --banner-ink (how far the
body copy is pulled from --fg toward the hue). The percentages differ per
tone deliberately — yellow carries far less weight than blue at the same
value, so warning washes harder and inks lighter to land at the same
perceived strength.
Source
src/components/ui/banner/banner.tsx — no next/* imports, so it drops into any
React 19 + Tailwind v4 app.
Badge
Badge: a status chip carried by fill alone — a tint of the tone at 12–15%, label inked toward the hue, and a sharp radius from --brand-radius-sm. Five tones (neutral, info, success, warning, danger) plus an optional icon slot. No runtime dependencies.
Button
Button component built on Base UI, styled with sc1m tokens.