DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
AlertBannerToastEmptyStateTooltipCoachMark
AI agentsContributing
  1. Components
  2. Banner

Banner

A page- or section-level announcement surface. Two layouts — the promotional panel (overline, headline, body, CTAs, media) and the thin strip bar (icon, message, end actions, dismiss) — with semantic intents, five palettes, and full light/dark support.

$ pnpm add @tessinaui/ui

When to use

  • A message about the whole page or a major section: an announcement, a promotion, or an account-level system message. Anything narrower belongs to Alert.
  • The thin strip for a one-line notice at the top of the page, with an optional action and dismiss.
  • The promotional panel when the message needs an overline, headline, body, call to action and media.
  • A semantic intent (info, success, warning, error) when the state of the system or account is the message. Severity also lives in the text, never colour alone.
  • One banner at a time. Combine messages before stacking; if you must stack, put the highest severity first.

When not to use

  • For feedback on one piece of content such as a form, a card or a field group. Use Alert, placed next to the thing it describes.
  • For form-validation errors. They belong to the form (FormErrorSummary), never to Banner.
  • For a brief confirmation after an action. Use a Toast.
  • For routine or non-critical messages. Warning and error banners persist and interrupt, so keep them for things the user must resolve, with an action or link that does.
  • For long copy. Keep the body to one or two sentences and put detail behind the action.
  • As a repeated promo. Gate it by eligibility, cap its frequency and persist its dismissal.

Installation

Terminal
pnpm add @tessinaui/ui

Usage

example.tsx
import { Banner } from "@tessinaui/ui";
example.tsx
{/* Announcement strip — the thin page-top bar */}
<Banner
  layout="strip"
  body={<>Free shipping on orders over $50. <a href="/shipping">See details</a></>}
  onDismiss={() => setHidden(true)}
/>

{/* System banner — semantic intent picks the plate, icon and role */}
<Banner
  layout="strip"
  intent="warning"
  headline="Verify your email."
  body="Confirm your address to keep sending messages."
  actions={[{ label: "Resend email", variant: "outline" }]}
/>

{/* Promotional panel */}
<Banner
  background="primary"
  overline="Limited time"
  headline="Summer styles, up to 40% off"
  body="Refresh your wardrobe with thousands of deals."
  actions={[{ label: "Shop the sale", href: "/sale" }]}
  disclaimer="Offer ends Sunday. Selected items only."
/>

{/* Single-destination — the whole banner is one link */}
<Banner layout="strip" href="/collection" headline="Discover the new collection" />

Compose the parts directly when you need full control:

example.tsx
import {
  Banner, BannerOverline, BannerHeadline, BannerBody, BannerActions, BannerDisclaimer,
} from "@tessinaui/ui";

<Banner background="primary">
  <BannerOverline>Limited time</BannerOverline>
  <BannerHeadline>Summer styles, up to 40% off</BannerHeadline>
  <BannerBody>Refresh your wardrobe with thousands of deals.</BannerBody>
  <BannerActions>{/* your own buttons/links */}</BannerActions>
  <BannerDisclaimer>Offer ends Sunday.</BannerDisclaimer>
</Banner>

Banner or Alert?

The house splits the two by scope — what the message governs, not how loud it is:

BannerAlert
ScopeWhole page or a major section — announcements, promos, account-level system messagesOne piece of content — a form, a card, a field group
PlacementEdge-to-edge above the content it governs (typically before the page h1), or a rounded block between sectionsIn the flow, next to the thing it describes
Shapestrip bar or promo panelMessage box with title/description/actions

Form-validation summaries belong to the form (see FormErrorSummary), never to Banner.

Curving your own content

Content you put inside — an inset image or video poster — should curve like the chrome around it, or it reads as pasted on. The banner root publishes its inner radius as a CSS variable:

example.tsx
<img className="rounded-[var(--banner-item-radius)]" />

It tracks whatever the component is actually doing: the explicit step when rounded is set (8px at md), and 0 at rounded="none" — so your content squares off exactly when the container does.

Examples

Default

Overline, headline, body, a call-to-action, and a disclaimer on the primary palette.

Strip

The thin announcement bar: inline link in the message, centered variant, an info mode-indicator with an end action, and a dismissible in-content strip.

Intents

info / success / warning / error in both weights. Icons come free per intent (override with icon, hide with icon={null}); severity always also lives in the text — never colour alone. Intents work on panels too.

Palettes

The five background fills — neutral, primary, inverse, accent, vivid — on panels and strips. inverse and vivid are locked plates: identical in light and dark.

Media

A full-bleed photo (with a legibility scrim), an inset side column, and an arbitrary decorative node with the overlay switched off.

Sizes

Panels scale padding, headline and body together. Strips take the same size prop but cap at md — the two strip densities differ only in row height, so they are not shown separately here.

Strip actions

An end button, a ghost text-action pair with a loading state, and a chevron drill-in over a whole-strip link.

Whole-banner link

href turns the entire banner into a single link, named by the headline. Explicit actions stay independently clickable above it.

Multi-destination

Pass several actions to point at multiple destinations.

Dismiss with persistence

A dismissed promo stays dismissed on return visits. Persistence is consumer state — three lines with localStorage here.

Countdown & promo code

Timer chips and applied-code chips are badge-slot compositions — the banner ships no timer API.

Threshold progress

“$X away from free shipping” — compose the house Progress in compound children; the amount lives in the text, the bar reinforces it.

App banner

An open-in-app banner from existing slots: a logo in icon, a rating line in body, Open + fallback in actions.

Stacked banners

Prefer ONE banner at a time (combine messages). When stacking is unavoidable, order by severity — error above warning.

Video background

Motion media must ship a pause control (WCAG 2.2.2) and must not autoplay under reduced motion. The video is decorative; the message lives in text.

Consent bar

The Accept/Reject pair is the dismissal — no ✕. Pin it with fixed inset-x-0 bottom-0 z-50 in production and pad the page bottom so content never hides behind it.

Success with focus

For post-action success, pick ONE mechanism. This example moves focus, so the banner is a labelled region rather than a live region — announcing it as well would read the same sentence twice. Whatever takes focus must hand it back on dismiss. Never script focus to banners that exist on page load.

On color

A banner does not sit on a plate — it paints one. primary, inverse, vivid, an image and a solid intent publish their ink, so the actions and the dismiss control dress from it with no tone prop. Banner does not read the tone of a Surface around it.

RTL

Banner has no dir prop and renders in place, so it follows the nearest dir ancestor. Under dir="rtl" the icon, message and actions start from the right and the dismiss control pins to the left corner.

API Reference

Anatomy

example.tsx
<Banner layout="panel">
  {/* icon?       — leading icon (auto per intent)   */}
  {/* badge?      — logo / chip slot                 */}
  <BannerOverline />   {/* eyebrow (panel only)      */}
  <BannerHeadline />   {/* heading (panel) / inline strong (strip) */}
  <BannerBody />       {/* supporting copy           */}
  <BannerActions />    {/* CTAs — end slot on strips */}
  <BannerDisclaimer /> {/* fine print (panel only)   */}
  {/* dismiss ✕   — when onDismiss is set            */}
</Banner>

Banner

PropTypeDefaultDescription
layoutpanel | strippanelPromotional hero vs thin announcement bar
intentnone | info | success | warning | errornoneSemantic banner: paints the plate, auto-picks icon + live-region role. Wins over background
variantsoft | solidsoftIntent plate weight (tint vs on-color fill). Ignored while intent="none"
backgroundneutral | primary | inverse | accent | vividneutralWhich fill to paint (Surface vocabulary). vivid/inverse are locked light-dark-identical plates
mediaimage | inset—Panel media: full-bleed photo or side column
sizesm | md | lgmdPanel scale; strips cap at md
roundednone | sm | md | lg | fullpanel lg · strip none§2 cascade; full caps at the 24px panel radius
alignstart | centerstartContent alignment
iconReactNode | nullauto per intentLeading icon; null hides; any node (emoji, logo, thumb) replaces
overlineReactNode—Eyebrow label (panel only)
headlineReactNode—Title — heading in panels, inline strong in strips
headingLevelh1–h6h3Panel heading tag
bodyReactNode—Supporting copy
disclaimerReactNode—Fine print (panel only)
actionsBannerAction[] | ReactNode—CTAs; first is prominent. Strips place them at the end
badgeReactNode—Chip/logo slot
image / imageAltstring | ReactNode / string—Media source for media
overlayauto | scrim | noneautoLegibility treatment over a photo
hrefstring—Whole-banner single link
onDismiss / dismissLabel() => void / string— / Dismiss <headline>Dismiss control; label auto-enriches with a string headline
rolestringby intentOverrides the live-region policy

BannerAction: { label, href?, onClick?, variant?, intent?, size?, rounded?, loading?, leadingIcon?, trailingIcon? }.

Deprecated aliases (one minor, dev-warn once)

OldNewNotes
tone="neutral | primary | inverse | accent | vivid"backgroundSame values — §1 reserves tone for the on-color axis
background="color | image | inset"media (+ palette on background)color → no media
rounded="xl"lgThe old 24px xl is the new full (§2 panel cap); full shrank 32→24px

BannerSkeleton

Mirrors layout / size / rounded / media / align plus count-exact structure: lines, actions, dismissible, showIcon, showOverline, showDisclaimer. The plate and every bar derive from the component's own maps (§2b) — the CTA placeholder IS the button box.

Accessibility

  • Live-region policy (override with role): error/warning → role="alert" (announced immediately); info/success → role="status" (polite); promotional banners → a labelled region when a prop headline names them, otherwise no role. Static page-load promos must never be alerts.
  • A dynamically inserted alert/status banner announces on mount; content that exists at first paint may not — mount it in response to the event.
  • Icons are decorative (aria-hidden) — severity always also lives in the text.
  • The dismiss label auto-enriches with a string headline ("Dismiss Summer sale"), so stacked banners stay distinguishable to AT.
  • With href the whole banner is one link named by the headline; explicit actions sit above it and stay independently clickable.
  • Strips render the headline as an inline <strong>-weight span — a bar must not mint a document heading. Panels render a real heading; set headingLevel to keep the outline valid.
  • Actions render house Buttons: on-color plates hand them the published surface ink automatically.
  • Keyboard: everything interactive is a real button/link in natural tab order; the banner container itself is deliberately not focusable (no tab noise). Focus-on-mount for success banners is a consumer effect — see the example.

Content guidance

  • One banner at a time; combine messages before stacking. If you must stack, order by severity.
  • warning/error banners should carry an action or link that resolves the problem.
  • Action labels are verb+noun ("Verify identity"), links destination-descriptive — never "Click here".
  • Body stays at 1–2 sentences; put detail behind the action.
  • Promos respect restraint: eligibility-gate and frequency-cap repeated banners, and persist dismissal (see the persistence example).

Agent notes

Stable hooks for tooling and AI agents: root data-slot="banner" with data-layout, data-intent/data-variant (semantic banners) or data-palette (promos); parts carry data-slot="banner-icon | banner-badge | banner-content | banner-overline | banner-headline | banner-body | banner-actions | banner-disclaimer | banner-media | banner-link | banner-dismiss". Async CTAs take loading per action. Content is free-form ReactNode everywhere; long copy wraps (strips reflow actions to a second row rather than truncating).

AlertToast

On this page

When to useWhen not to useInstallationUsageBanner or Alert?Curving your own contentExamplesDefaultStripIntentsPalettesMediaSizesStrip actionsWhole-banner linkMulti-destinationDismiss with persistenceCountdown & promo codeThreshold progressApp bannerStacked bannersVideo backgroundConsent barSuccess with focusOn colorRTLAPI ReferenceAnatomyBannerDeprecated aliases (one minor, dev-warn once)BannerSkeletonAccessibilityContent guidanceAgent notes