Deck
Browse the catalog

Build / Molecules

Banner

  • molecule
  • play-fn

An inline message attached to the surface it concerns.

Deck's workhorse for CRM sync failures, plan-cap warnings, the recipient "create your own MeetCard" invite, and offline notices.

error and warning announce themselves via role="alert"; the quieter tones use role="status" so they don't interrupt. Tone is never the only signal — always write a title or body that carries the meaning.

import { Banner } from '@meetcard/deck'

Example

<Banner tone="warning" title="You're near your Solo limit" actions={<Button size="sm">Upgrade</Button>}> 92 of 100 connections used. </Banner>

Stories

Every preview below is the real component, rendered in this page from the same story Storybook runs. The width control resizes each preview's container; CSS media queries still follow the browser window, so breakpoint behaviour is worth checking in Storybook.

Default
Source
const Default = () => <div style={{ maxWidth: 560 }}>
    <Banner title="Heads up">Something worth knowing about.</Banner>
</div>;

Every tone. Pick by what the reader has to do next, not by how serious it sounds.

Tones
Source
const Tones = (args) => (
  <Stack gap={12} style={{ maxWidth: 560 }}>
    {(['info', 'brand', 'success', 'warning', 'error'] as const).map((tone) => (
      <Banner {...args} key={tone} tone={tone} title={tone}>
        Tone is never the only signal — the copy carries the meaning.
      </Banner>
    ))}
  </Stack>
);

A CRM write failed. Errors announce themselves via role="alert".

Sync Failed
Source
const SyncFailed = () => <div style={{ maxWidth: 560 }}>
    <Banner
        title="CRM sync failed"
        tone="error"
        actions={<Button size="sm" variant="secondary">Retry</Button>}>3 connections could not be written to Salesforce.</Banner>
</div>;

Nearing the Solo cap — an upgrade prompt at the moment of realised value.

Plan Cap
Source
const PlanCap = () => <div style={{ maxWidth: 560 }}>
    <Banner
        title="You're close to your Solo limit"
        tone="warning"
        actions={<Button size="sm">Upgrade</Button>}>92 of 100 connections used.</Banner>
</div>;

The recipient conversion invite. It must never block or degrade the core save action, so it is a quiet inline banner rather than a modal.

Recipient Invite
Source
const RecipientInvite = () => <div style={{ maxWidth: 560 }}>
    <Banner
        title="Want a card like this?"
        tone="brand"
        actions={<Button size="sm">Create my card</Button>}
        onDismiss={fn()}>Create your own MeetCard in under a minute — free.</Banner>
</div>;

Offline notice — the PWA keeps working, writes queue for background sync.

Offline
Source
const Offline = () => <div style={{ maxWidth: 560 }}>
    <Banner title="You're offline" tone="info">Your card still shows. Changes will sync when you reconnect.</Banner>
</div>;

No title, for a single sentence that is its own headline.

Without Title
Source
const WithoutTitle = () => <div style={{ maxWidth: 560 }}>
    <Banner title={undefined}>A single-line message with no title.</Banner>
</div>;

Props

PropTypeDefaultDescription
actionsReactNode—Trailing controls, e.g. an "Upgrade" or "Retry sync" Button.
dismissLabelstring'Dismiss'—
onDismiss() => void—Shows a dismiss control. Dismissal state is the caller's to hold.
titleReactNode——
tone'info' | 'success' | 'warning' | 'error' | 'brand''info'—

Also accepts everything from Omit, so id, data-* and event handlers pass straight through.

Open in Storybookfor controls, the a11y panel and the interaction runner.