Full-width message strip for cookie consent, alerts, and announcements. Sits in the page flow, sticks to the top or bottom of a section, or fixes to the viewport. Visibility and variant are driven by data attributes.

Structure
Class Description
.mdst-banner Banner container. Use role="region" with an aria-label for persistent banners (cookies), or role="status" / role="alert" for messages that appear at runtime
.mdst-banner-content Message text. Grows to fill the row and pushes actions to the end
.mdst-banner-actions Group of buttons or links. Wraps below the content on narrow screens
.mdst-banner-close Dismiss button
States
Data Attribute Description
data-state="visible" Banner is shown and fades in
data-state="hidden" Banner fades out and is removed from layout (display: none)
Variants
Default banner, no variant.
info: scheduled maintenance on Sunday at 02:00 UTC.
success: your account has been verified.
warning: your trial ends in 3 days.
error: we couldn’t process your last payment.
With Actions
We use cookies

We use analytics cookies to understand how the site is used. You can accept or reject them.

Sizes
Class Example
.mdst-banner
Default size.
.mdst-banner--sm
Compact padding and smaller text.
Borders
Class Example
.mdst-banner--borderless
No border.
.mdst-banner--border-top
Top edge only.
.mdst-banner--border-bottom
Bottom edge only.
.mdst-banner--border-start
.mdst-banner--border-end
Start and end edges. Edge classes combine.

Any border class removes the other edges. A data-variant accent edge is always kept.

Positions
Class Description
.mdst-banner In the flow of the page (default)
.mdst-banner--top
.mdst-banner--bottom
Sticks to the top or bottom edge of its section while it scrolls. Place --top as the section’s first child and --bottom as its last
.mdst-banner--fixed Fixed to the viewport. Combine with --top or --bottom
.mdst-banner--start
.mdst-banner--end
With --fixed: a compact card in the start or end corner instead of full width. Goes full width below 30rem
--mdst-banner-offset Distance from the edge. Defaults to 0 for sticky and var(--mdst-space-md) for fixed. Set to 0 for an edge-to-edge fixed banner
--mdst-banner-width Maximum width of a corner banner. Defaults to 28rem
Sticky in a Section
--top stays pinned while this section scrolls.

Scroll this section. The banners stay attached to its top and bottom edges but never leave it.

Sticky banners take up space in the layout, so they never overlap the first or last line of content.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

--bottom stays pinned to the bottom edge.
Interactive Demo: Cookie Consent

A fixed corner cookie banner (--fixed --bottom --end --sm) wired to Google Analytics Consent Mode. The JavaScript below is not part of modest-ui — it’s an example of the logic you’d add where you use the banner. The CSS only responds to data-state. This demo doesn’t load Google Analytics; the calls it would send are logged below instead.

Demo source
<!-- 1. In <head>, before the GA tag: deny everything by default -->
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag() { dataLayer.push(arguments); }

  gtag("consent", "default", {
    ad_storage: "denied",
    ad_user_data: "denied",
    ad_personalization: "denied",
    analytics_storage: "denied",
  });
</script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  gtag("js", new Date());
  gtag("config", "G-XXXXXXXXXX");
</script>

<!-- 2. The banner, hidden until we know there's no stored choice -->
<div class="mdst-banner mdst-banner--sm mdst-banner--fixed mdst-banner--bottom mdst-banner--end"
     id="cookie-banner" data-state="hidden"
     role="region" aria-label="Cookie consent">
  <p class="mdst-banner-content">
    We use Google Analytics cookies to see how the site is used.
    <a href="/privacy/">Privacy</a>
  </p>
  <div class="mdst-banner-actions">
    <button class="mdst-button mdst-button--sm mdst-button--inverted" data-consent="granted">Accept</button>
    <button class="mdst-button mdst-button--sm mdst-button--ghost" data-consent="denied">Decline</button>
  </div>
</div>

<!-- 3. Show the banner, store the choice, update GA -->
<script>
  const STORAGE_KEY = "cookie-consent";
  const banner = document.querySelector("#cookie-banner");

  function readConsent() {
    try { return localStorage.getItem(STORAGE_KEY); } catch { return null; }
  }

  function applyConsent(value) {
    gtag("consent", "update", {
      ad_storage: value,
      ad_user_data: value,
      ad_personalization: value,
      analytics_storage: value,
    });
  }

  function choose(value) {
    try { localStorage.setItem(STORAGE_KEY, value); } catch {}
    applyConsent(value);
    banner.setAttribute("data-state", "hidden");
  }

  banner.querySelectorAll("[data-consent]").forEach((btn) => {
    btn.addEventListener("click", () => choose(btn.dataset.consent));
  });

  const stored = readConsent();
  if (stored) {
    applyConsent(stored);
  } else {
    banner.setAttribute("data-state", "visible");
  }
</script>
Usage
<!-- In the flow of the page -->
<div class="mdst-banner" data-variant="info" role="region" aria-label="Announcement">
  <div class="mdst-banner-content">Scheduled maintenance on Sunday.</div>
  <button class="mdst-banner-close" aria-label="Dismiss">✕</button>
</div>

<!-- Sticky to the top of a scrolling section -->
<section style="overflow-y: auto">
  <div class="mdst-banner mdst-banner--top mdst-banner--border-bottom">...</div>
  ...
</section>

<!-- Fixed to the bottom of the viewport (cookie consent) -->
<div class="mdst-banner mdst-banner--fixed mdst-banner--bottom"
     data-state="visible" role="region" aria-label="Cookie consent">
  <div class="mdst-banner-content">We use analytics cookies.</div>
  <div class="mdst-banner-actions">
    <button class="mdst-button mdst-button--sm">Reject</button>
    <button class="mdst-button mdst-button--sm mdst-button--inverted">Accept</button>
  </div>
</div>

<!-- Compact card fixed to the bottom-right corner -->
<div class="mdst-banner mdst-banner--sm mdst-banner--fixed mdst-banner--bottom mdst-banner--end"
     data-state="visible" role="region" aria-label="Cookie consent">...</div>

<!-- Edge-to-edge fixed banner -->
<div class="mdst-banner mdst-banner--fixed mdst-banner--top" style="--mdst-banner-offset: 0">...</div>

<!-- Example JS -->
<script>
  function showBanner(el) {
    el.setAttribute("data-state", "visible");
  }
  function hideBanner(el) {
    el.setAttribute("data-state", "hidden");
  }
</script>