Skip to content

Banner

A banner is a page- or app-level announcement strip: a single full-bleed horizontal row (leading icon + message + optional inline action + dismiss) that spans its container edge-to-edge with no rounded card border, optionally pinned to the top. Use it for cookie notices, maintenance windows, promos, and system status.

It is distinct from alert — alert is a rounded, inset, always-subtle inline status card; banner is a borderless full-bleed band and adds a data-emphasis="solid" fill that alert never has. Put it in a full-width slot (outside a centered max-width wrapper) to reach the edges.

The status tone is data-tone: info (default), success, warning, danger.

Live example
<div style="display: flex; flex-direction: column; gap: var(--re-space-3)">
  <aside class="re-banner" role="region" aria-label="Announcement">
    <span class="re-banner__icon" aria-hidden="true">i</span>
    <p class="re-banner__message">
      We use cookies to improve your experience.
      <a class="re-banner__action" href="#privacy">Learn more</a>
    </p>
  </aside>
  <aside class="re-banner" data-tone="success" role="region" aria-label="Status">
    <span class="re-banner__icon" aria-hidden="true">✓</span>
    <p class="re-banner__message">All systems operational.</p>
  </aside>
  <aside class="re-banner" data-tone="warning" role="region" aria-label="Notice">
    <span class="re-banner__icon" aria-hidden="true">!</span>
    <p class="re-banner__message">
      Your trial ends in 3 days.
      <a class="re-banner__action" href="#billing">Upgrade now</a>
    </p>
  </aside>
  <aside class="re-banner" data-tone="danger" role="region" aria-label="System status">
    <span class="re-banner__icon" aria-hidden="true">!</span>
    <p class="re-banner__message">
      Scheduled maintenance tonight 02:00–03:00 UTC.
      <a class="re-banner__action" href="#status">View status</a>
    </p>
  </aside>
</div>

data-emphasis="solid" swaps the tint for a bold fill — the *-700 status scale with white text, which clears WCAG AA contrast. Reserve it for genuinely high-priority strips.

Live example
<div style="display: flex; flex-direction: column; gap: var(--re-space-3)">
  <aside class="re-banner" data-emphasis="solid" role="region" aria-label="Promo">
    <span class="re-banner__icon" aria-hidden="true">★</span>
    <p class="re-banner__message">
      Black Friday: 40% off all plans this week.
      <a class="re-banner__action" href="#pricing">See plans</a>
    </p>
  </aside>
  <aside
    class="re-banner"
    data-tone="success"
    data-emphasis="solid"
    role="region"
    aria-label="Status"
  >
    <span class="re-banner__icon" aria-hidden="true">✓</span>
    <p class="re-banner__message">Payment received — your account is now active.</p>
  </aside>
  <aside
    class="re-banner"
    data-tone="warning"
    data-emphasis="solid"
    role="region"
    aria-label="Notice"
  >
    <span class="re-banner__icon" aria-hidden="true">!</span>
    <p class="re-banner__message">
      Action required: confirm your email address.
      <a class="re-banner__action" href="#verify">Confirm</a>
    </p>
  </aside>
  <aside
    class="re-banner"
    data-tone="danger"
    data-emphasis="solid"
    role="region"
    aria-label="Incident"
  >
    <span class="re-banner__icon" aria-hidden="true">!</span>
    <p class="re-banner__message">
      We are investigating an active service disruption.
      <a class="re-banner__action" href="#status">Live updates</a>
    </p>
  </aside>
</div>

Mark the host [data-re-dismissible], add a [data-re-dismiss] button, and call enhanceDismissible() (the same behavior alert and toast use).

Live example
<aside
  class="re-banner"
  data-tone="warning"
  role="region"
  aria-label="Announcement"
  data-re-dismissible
  id="dismissible-banner"
>
  <span class="re-banner__icon" aria-hidden="true">!</span>
  <p class="re-banner__message">
    This banner can be dismissed.
    <a class="re-banner__action" href="#privacy">Learn more</a>
  </p>
  <button type="button" class="re-banner__dismiss" data-re-dismiss aria-label="Dismiss">
    ×
  </button>
</aside>

data-align="center" caps the message at a readable measure and centers it while the fill stays full-bleed — the classic announcement-bar look.

Live example
<aside
  class="re-banner"
  data-emphasis="solid"
  data-align="center"
  role="region"
  aria-label="Promo"
>
  <p class="re-banner__message">
    Free shipping on every order this weekend.
    <a class="re-banner__action" href="#shop">Shop now</a>
  </p>
</aside>

data-sticky pins the banner to the top of its scroll container while content scrolls underneath. Under a fixed site header, override --re-banner-sticky-top.

Scroll this region — the banner stays pinned to the top.

More content below to enable scrolling.

Even more content.

Keep scrolling.

Almost there.

Live example
<div
  style="
    block-size: 9rem;
    overflow: auto;
    border: var(--re-border-default);
    border-radius: var(--re-radius-md);
  "
>
  <aside
    class="re-banner"
    data-tone="danger"
    data-emphasis="solid"
    data-sticky
    role="region"
    aria-label="System status"
    data-re-dismissible
  >
    <span class="re-banner__icon" aria-hidden="true">!</span>
    <p class="re-banner__message">
      Scheduled maintenance tonight 02:00–03:00 UTC.
      <a class="re-banner__action" href="#status">View status</a>
    </p>
    <button type="button" class="re-banner__dismiss" data-re-dismiss aria-label="Dismiss">
      ×
    </button>
  </aside>
  <div style="padding: var(--re-space-4); display: grid; gap: var(--re-space-3)">
    <p>Scroll this region — the banner stays pinned to the top.</p>
    <p>More content below to enable scrolling.</p>
    <p>Even more content.</p>
    <p>Keep scrolling.</p>
    <p>Almost there.</p>
  </div>
</div>
  • Sticky: data-sticky pins the banner to the top of its scroll container. Under a fixed site header, override --re-banner-sticky-top in one line.
  • Persistence: enhanceDismissible only sets [hidden] for the session. A cookie notice that must stay dismissed across reloads needs your app to persist that choice (e.g. localStorage) and conditionally render — by design, the library adds no new JavaScript here.
  • Full-bleed is plain inline-size: 100% (no calc(50% - 50vw) hack): the band fills whatever slot you place it in.
  • Keyboard — the banner itself is not focusable; the only interactive parts are native. The inline .re-banner__action link is a real <a href> (Tab to focus, Enter to follow). The [data-re-dismiss] close button is a native <button>, so Enter and Space activate it — enhanceDismissible also handles Enter/Space itself and hides the closest [data-re-dismissible] host.
  • Focus — the action link and dismiss button show a visible :focus-visible ring (--re-shadow-focus); the dismiss ring is an outer ring, since the band’s block padding gives it room to render unclipped. There is no focus trap — a banner is inline page content, not a modal overlay, so focus is never captured or returned.
  • Semantics — mark up a persistent strip as <aside> or <div role="region"> with an aria-label; for a strip injected at runtime, use role="status" (a polite live region) so assistive tech announces it without stealing focus. The leading .re-banner__icon is decorative and carries aria-hidden="true", and the dismiss button has an explicit aria-label (“Dismiss”) because its only child is a × glyph.
  • Notes — solid emphasis (data-emphasis="solid") uses the *-700 status fill with text-on-accent text, and the dismiss × keeps full currentColor (not a translucent mix), so message, link, icon, and close button all clear WCAG AA contrast on both subtle and solid fills, in light and dark mode. Logical properties (margin-inline-start, border-block) keep the layout and the inline-end dismiss button correct under dir="rtl". See the accessibility guide for the system-wide baseline.