DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

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.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import { Banner } from "@tessinaui/ui";
{/* 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:

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 follows the Atlassian Banner / SectionMessage split:

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:

<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

The Smart-App-Banner rule: 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

Smart-app-banner anatomy 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

The GOV.UK model 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.

Anatomy

<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>

Props

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, per Astryx guidance).
  • 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).

BadgeBar Chart

On this page

PlaygroundInstallationUsageBanner or Alert?Curving your own contentExamplesDefaultStripIntentsPalettesMediaSizesStrip actionsWhole-banner linkMulti-destinationDismiss with persistenceCountdown & promo codeThreshold progressApp bannerStacked bannersVideo backgroundConsent barSuccess with focusAnatomyPropsBannerDeprecated aliases (one minor, dev-warn once)BannerSkeletonAccessibilityContent guidanceAgent notes