# Banner

**Maturity:** stable

A persistent, in-flow bar that surfaces a short status or notification without blocking the page: a status glyph, a title, an optional secondary message, an optional row of trailing actions, and an optional dismiss control.

## Import

```tsx
import { Banner } from "@liquidify/react/banner"
```

## Props

- `actions`: `readonly BannerAction[] | undefined`. Trailing action row, keyed by each action's stable `id`.
- `className`: `string | undefined`. Optional additional CSS class merged into the glass surface class. The supported per-instance customisation seam: override any `--lq-*` custom property from this class's CSS — the token cascade the engine reads (ADR-0004). No raw inline style is involved, so this stays ADR-0003 clean.
- `defaultOpen`: `boolean | undefined`; default ``true``. Uncontrolled seed for the open state — a banner passed no `open` renders visible.
- `dismissible`: `boolean | undefined`; default ``true``. Renders the trailing `xmark` dismiss control when `true`.
- `dismissIcon`: `ReactNode`. Custom dismiss glyph — an SF-Symbol-style name string or a node (the `Image(systemName:)` analogue, mirroring {@link BannerProps.icon}); when omitted the default self-drawn `xmark` cross renders. Purely visual: the control keeps its `aria-label` ({@link BannerProps.dismissLabel}) either way.
- `dismissLabel`: `string | undefined`; default ``"Dismiss"``. Accessible name override for the dismiss control.
- `icon`: `ReactNode`. Leading status glyph. An SF-Symbol-style name string or a node; when omitted a status-default glyph is derived from {@link BannerProps.status}.
- `id`: `string | undefined`. Native `id` forwarded to the bar root (charter §8: no silently-discarded inherited props).
- `items`: `readonly ReactNode[] | undefined`. Optional bulleted supporting points. Each item receives the same native Banner message typography configured through `messageProps`.
- `labelProps`: `LabelProps | undefined`. Per-instance typography override for the title, forwarded to the internal `Text` (ADR-0028). Spread last over the computed default.
- `live`: `boolean | undefined`; default ``true``. When `false`, the banner is a static, labelled `role="region"` (not a live region) — for banners present at page load rather than dynamically raised.
- `material`: `"frosted" | "regular" | "clear" | undefined`; default `enclosing `<Material default>`, else `"regular"``. Material of the glass surface — `frosted · regular · clear` (§06). Forwarded to the engine, which resolves the chain `prop ?? <Material default> ?? "regular"`, so a bare surface inherits the enclosing provider default.
- `message`: `ReactNode`. Optional secondary copy; the `aria-describedby` target.
- `messageProps`: `LabelProps | undefined`. Per-instance typography override for the message, forwarded to the internal `Text` (ADR-0028). Spread last over the `footnote`/`secondary` default.
- `onOpenChange`: `((open: boolean) => void) | undefined`. Fires with the next boolean on every close path (dismiss, or `closesOnPress` action).
- `open`: `boolean | undefined`. Controlled open state. Passing it switches Banner to controlled mode.
- `perf`: `"low" | "high" | undefined`; default `derived from `<Backdrop type>`, else `"low"``. Perf budget of the glass surface (ADR-0006/0007). Forwarded to the engine as `data-glass-perf`; `"high"` opts this surface into the edge-lensing filter. When omitted, derives from the enclosing `<Backdrop type>` (§04).
- `ref`: `Ref<HTMLDivElement> | undefined`. Forwarded ref to the rendered element — the bar `<div>` carrying the a11y role; attached while `open` and `null` while dismissed.
- `status`: `BannerStatus | undefined`; default ``"info"``. Severity axis — selects the default glyph + accent colour (glyph/edge only, never a full-surface wash, ADR-0019) and the derived a11y `role`.
- `title`: `ReactNode` (required). The bar's primary content — required, the `aria-labelledby` target.

## SwiftUI mapping

- ``Label(_ title:, systemImage:)`` → ``title: string` (+ `icon?`)` (direct): the icon+title pairing; `title` is required and is the accessible name
- ``Image(systemName:)` status glyph` → ``icon?: string | ReactNode`` (direct): SF-Symbol-style name string or a node; when omitted a **status-default glyph** is derived from `status`
- ``Text(_:)` (secondary line)` → ``message?: string | ReactNode`` (direct): optional secondary copy; the `aria-describedby` target
- ``Image(systemName:)` severity family` → ``status?: "info" | "success" | "warning" | "error" | "neutral"`` (direct): severity axis; drives default glyph + accent colour (glyph/edge only), and the a11y role (see Accessibility)
- ``Button(_:action:)` in a trailing slot` → ``actions?: BannerAction[]`` (direct): `{ id, label, role?, onPress?, disabled?, closesOnPress? }`; each carries a stable, consumer-supplied `id` — rows key by `id` (never `role+label`), duplicate labels are supported, a duplicate `id` is a dev-time error
- ``Button.id` (collection identity)` → ``BannerAction.id: string`` (direct): required; the React key + collection identity (audit §16)
- ``Button(role: .destructive)`` → ``BannerAction.role: "destructive"`` (direct): red label (`--lq-color-action-destructive`); reuses Alert's role palette
- ``Button(role: .cancel)`` → ``BannerAction.role: "cancel"`` (direct): neutral bold label
- ``Button(…).disabled(_:)`` → ``BannerAction.disabled?: boolean`` (direct): Shape N — native `disabled` on `<Button>`, no `aria-disabled`; hard-suppressed (no `onPress`)
- `(undo-style coupling)` → ``BannerAction.closesOnPress?: boolean`` (direct): opt-in per-action close; default `false` — banner actions do **not** auto-close (unlike Alert; resolved Q5 — iOS-faithful: an inline status bar's action does not implicitly dismiss it)
- `dismiss gesture (analogue only)` → ``dismissible?: boolean`` (direct): renders the trailing `xmark` control; default `true` (resolved Q4 — orchestrator decision, 2026-07-16)
- ``isPresented: Binding<Bool>` (analogue)` → ``open` / `defaultOpen` / `onOpenChange`` (direct): presentation (open) triad (ADR-0015); **not** a value triad — Banner holds no `value`
- ``.font(_:)` / `.foregroundStyle(_:)` on the title` → ``labelProps?: LabelProps`` (direct): shared `Pick<TextProps,…>`; spread **last** into the title `TextLabel` (ADR-0028)
- ``.font(_:)` / `.foregroundStyle(_:)` on the message` → ``messageProps?: LabelProps`` (direct): spread **last** into the message `TextLabel`
- `(presentation chrome)` → ``material?` / `perf?`` (direct): `PresentationalProps` — the bar's glass surface; the base is tint-free (status carried by glyph + edge, not a surface wash)
- `(no SwiftUI analogue)` → ``ref?: Ref<HTMLDivElement>`` (web-only): forwarded to the semantic root (the bar `<div>` carrying the a11y role); attached while `open`, `null` while dismissed
