20 CSS Banners & Alerts05 / 20

Light JSMIT licensed

Maintenance and Incident Status Bar

A status bar that a real on-call engineer could ship: four severity levels driven entirely by data-severity, a component-scoped update timeline that expands in place, per-service health rows, and an aria-live region that speaks once per state change instead of on every repaint. Change one attribute and the palette, icon, label and pulse all follow.

Published

Live Demo
Try it

The code

<div class="ba-05">
  <div class="ba-05__bar" data-bar data-severity="major" role="region" aria-labelledby="ba-05-h">
    <div class="ba-05__main">
      <span class="ba-05__dot" aria-hidden="true"></span>
      <div class="ba-05__copy">
        <p class="ba-05__h" id="ba-05-h">
          <span class="ba-05__chip" data-label>Major outage</span>
          <span class="ba-05__msg" data-msg>API writes are failing in eu-west-1. Reads and the dashboard are unaffected.</span>
        </p>
        <p class="ba-05__stamp">
          Updated <time datetime="2026-08-10T09:12:00Z" data-time>09:12 UTC</time>
          <span aria-hidden="true">·</span> Next update by <time datetime="2026-08-10T09:45:00Z">09:45 UTC</time>
        </p>
      </div>
    </div>

    <div class="ba-05__acts">
      <div class="ba-05__switch" role="group" aria-label="Simulate incident severity">
        <button type="button" data-sev="operational">Operational</button>
        <button type="button" data-sev="maintenance">Maintenance</button>
        <button type="button" data-sev="degraded">Degraded</button>
        <button type="button" data-sev="major" aria-pressed="true">Major</button>
      </div>
      <button class="ba-05__sub" type="button" data-sub>Subscribe</button>
    </div>

    <details class="ba-05__log">
      <summary>
        <span>Incident timeline</span>
        <svg viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path d="m4 6.2 4 4 4-4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
      </summary>
      <ol class="ba-05__timeline">
        <li data-phase="monitoring"><p class="ba-05__tHead">Monitoring <time datetime="2026-08-10T09:12:00Z">09:12</time></p><p>Failover to the standby cluster completed. Write latency is back inside SLO; we are watching replication lag.</p></li>
        <li data-phase="identified"><p class="ba-05__tHead">Identified <time datetime="2026-08-10T08:54:00Z">08:54</time></p><p>A schema migration exhausted the connection pool on the primary. Migration rolled back.</p></li>
        <li data-phase="investigating"><p class="ba-05__tHead">Investigating <time datetime="2026-08-10T08:41:00Z">08:41</time></p><p>Elevated 5xx on POST endpoints in eu-west-1. Paging the database on-call.</p></li>
      </ol>
    </details>

    <ul class="ba-05__services">
      <li data-health="up"><span aria-hidden="true"></span>Dashboard<b>Operational</b></li>
      <li data-health="down"><span aria-hidden="true"></span>REST API (writes)<b>Outage</b></li>
      <li data-health="up"><span aria-hidden="true"></span>REST API (reads)<b>Operational</b></li>
      <li data-health="warn"><span aria-hidden="true"></span>Webhooks<b>Delayed</b></li>
      <li data-health="up"><span aria-hidden="true"></span>CDN<b>Operational</b></li>
    </ul>
  </div>

  <main class="ba-05__page">
    <h1>Platform</h1>
    <p>Region eu-west-1 · Plan Scale · 14 projects</p>
    <div class="ba-05__cards" aria-hidden="true"><span></span><span></span><span></span><span></span></div>
  </main>
  <p class="ba-05__live" role="status" aria-live="polite"></p>
</div>
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap');

.ba-05 {
  --bg: #08090c;
  --surface: #101319;
  --ink: #eef1f6;
  --muted: #98a0ad;
  --line: #20252e;
  --green: #22c55e;
  --amber: #f59e0b;
  --red: #f04438;
  --blue: #3b82f6;
  --sans: "Sora",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono: "JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  container-type: inline-size;
  interpolate-size: allow-keywords;
  position: relative;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr;
  box-sizing: border-box;
  scroll-padding-block-start: 6rem;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
}

.ba-05 *,
.ba-05 *::before,
.ba-05 *::after {
  box-sizing: border-box;
}

@supports (color: oklch(50% 0 0)) {
  .ba-05 {
    --bg: oklch(14% .012 265);
    --surface: oklch(20% .018 265);
    --ink: oklch(96% .006 265);
    --muted: oklch(71% .02 265);
    --line: oklch(29% .022 265);
    --green: oklch(72% .17 150);
    --amber: oklch(78% .16 78);
    --red: oklch(64% .21 26);
    --blue: oklch(65% .18 255);
  }
}

@media (prefers-color-scheme: light) {
  .ba-05:not([data-theme="dark"]) {
    --bg: #f7f8fa;
    --surface: #ffffff;
    --ink: #0d1016;
    --muted: #5b6472;
    --line: #e4e7ec;
  }
}

.ba-05[data-theme="light"] {
  --bg: #f7f8fa;
  --surface: #ffffff;
  --ink: #0d1016;
  --muted: #5b6472;
  --line: #e4e7ec;
}

.ba-05__bar {
  --sev: var(--red);
  --sevSoft: color-mix(in oklab,var(--sev) 13%,var(--surface));
  position: sticky;
  top: 0;
  z-index: 5;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .75rem clamp(1rem,3cqw,2rem);
  padding: clamp(.85rem,2vw,1.15rem) clamp(1rem,4vw,2rem);
  background: var(--sevSoft);
  border-block-end: 1px solid color-mix(in oklab,var(--sev) 30%,var(--line));
  box-shadow: 0 1px 0 0 color-mix(in oklab,var(--sev) 40%,transparent), 0 1.5rem 3rem -2rem rgb(0 0 0 / .6);
  transition: background .5s ease, border-color .5s ease;
}

.ba-05__bar[data-severity="operational"] {
  --sev: var(--green);
}

.ba-05__bar[data-severity="maintenance"] {
  --sev: var(--blue);
}

.ba-05__bar[data-severity="degraded"] {
  --sev: var(--amber);
}

.ba-05__bar[data-severity="major"] {
  --sev: var(--red);
}

.ba-05__main {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .85rem;
  align-items: start;
  min-width: 0;
}

.ba-05__dot {
  position: relative;
  display: block;
  width: .7rem;
  height: .7rem;
  margin: .5rem .25rem;
  border-radius: 999px;
  background: var(--sev);
  box-shadow: 0 0 0 .3rem color-mix(in oklab,var(--sev) 22%,transparent);
  flex: none;
}

.ba-05__dot::before,
.ba-05__dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 999px;
  border: 1.5px solid var(--sev);
  animation: ba-05-ping 2.6s cubic-bezier(.2,.6,.3,1) infinite;
}

.ba-05__dot::after {
  animation-delay: 1.3s;
}

@keyframes ba-05-ping {
  0% {
    scale: 1;
    opacity: .7;
  }

  80%,
    100% {
    scale: 3.4;
    opacity: 0;
  }
}

.ba-05__bar[data-severity="operational"] .ba-05__dot::before,
.ba-05__bar[data-severity="operational"] .ba-05__dot::after {
  animation: none;
  opacity: 0;
}

.ba-05__copy {
  min-width: 0;
  display: grid;
  gap: .3rem;
}

.ba-05__h {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: .6rem;
  flex-wrap: wrap;
  min-width: 0;
}

.ba-05__chip {
  flex: none;
  font-family: var(--mono);
  font-size: .625rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  padding: .28rem .5rem;
  border-radius: .4rem;
  color: var(--sev);
  background: color-mix(in oklab,var(--sev) 16%,transparent);
  border: 1px solid color-mix(in oklab,var(--sev) 34%,transparent);
}

.ba-05__msg {
  font-size: clamp(.8125rem,1.7cqw,.9375rem);
  line-height: 1.5;
  letter-spacing: -.012em;
  max-width: 72ch;
  text-wrap: pretty;
}

.ba-05__stamp {
  margin: 0;
  font-family: var(--mono);
  font-size: .6875rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  display: flex;
  gap: .4rem;
  flex-wrap: wrap;
}

.ba-05__acts {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
  justify-content: end;
}

.ba-05__switch {
  display: flex;
  padding: .2rem;
  gap: .15rem;
  border-radius: .75rem;
  background: color-mix(in oklab,var(--ink) 8%,transparent);
  border: 1px solid var(--line);
}

.ba-05__switch button {
  min-height: 2.25rem;
  padding: .4rem .65rem;
  border: 0;
  border-radius: .6rem;
  cursor: pointer;
  font: inherit;
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--muted);
  background: transparent;
  transition: color .2s ease, background .2s ease;
}

.ba-05__switch button:hover {
  color: var(--ink);
}

.ba-05__switch button[aria-pressed="true"] {
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 1px 0 rgb(255 255 255 / .06) inset;
}

.ba-05__sub {
  min-height: 2.25rem;
  padding: .45rem .9rem;
  border-radius: .7rem;
  cursor: pointer;
  font: inherit;
  font-size: .75rem;
  font-weight: 700;
  color: var(--sev);
  background: transparent;
  border: 1px solid color-mix(in oklab,var(--sev) 40%,transparent);
  transition: background .2s ease;
}

.ba-05__sub:hover {
  background: color-mix(in oklab,var(--sev) 14%,transparent);
}

.ba-05__sub[data-on]::after {
  content: " ✓";
}

.ba-05__log {
  grid-column: 1 / -1;
  border-block-start: 1px solid var(--line);
  margin-block-start: .25rem;
}

.ba-05__log summary {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  min-height: 2.5rem;
  padding: .5rem 0;
  cursor: pointer;
  list-style: none;
  font-size: .75rem;
  font-weight: 600;
  color: var(--muted);
}

.ba-05__log summary::-webkit-details-marker {
  display: none;
}

.ba-05__log summary:hover {
  color: var(--ink);
}

.ba-05__log summary svg {
  width: .9rem;
  height: .9rem;
  transition: rotate .3s ease;
}

.ba-05__log[open] summary svg {
  rotate: 180deg;
}

.ba-05__log::details-content {
  block-size: 0;
  overflow: clip;
  opacity: 0;
  transition: block-size .4s cubic-bezier(.16,1,.3,1), opacity .3s ease, content-visibility .4s allow-discrete;
}

.ba-05__log[open]::details-content {
  block-size: auto;
  opacity: 1;
}

.ba-05__timeline {
  margin: 0 0 .75rem;
  padding: 0 0 0 1.1rem;
  list-style: none;
  display: grid;
  gap: .85rem;
  max-block-size: 14rem;
  overflow: auto;
  overscroll-behavior: contain;
  border-inline-start: 2px solid color-mix(in oklab,var(--sev) 35%,var(--line));
}

.ba-05__timeline li {
  position: relative;
  padding-inline-start: .35rem;
}

.ba-05__timeline li::before {
  content: "";
  position: absolute;
  inset-inline-start: -1.55rem;
  inset-block-start: .35rem;
  width: .55rem;
  height: .55rem;
  border-radius: 999px;
  background: var(--sev);
  box-shadow: 0 0 0 3px var(--sevSoft);
}

.ba-05__timeline li[data-phase="investigating"]::before {
  background: var(--muted);
}

.ba-05__tHead {
  margin: 0 0 .2rem;
  display: flex;
  gap: .5rem;
  align-items: baseline;
  font-family: var(--mono);
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--sev);
}

.ba-05__tHead time {
  color: var(--muted);
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

.ba-05__timeline p:last-child {
  margin: 0;
  font-size: .8125rem;
  line-height: 1.55;
  color: var(--muted);
  max-width: 70ch;
  text-wrap: pretty;
}

.ba-05__services {
  grid-column: 1 / -1;
  margin: 0;
  padding: .65rem 0 0;
  list-style: none;
  display: grid;
  gap: .35rem .9rem;
  grid-template-columns: repeat(auto-fit,minmax(min(100%,11rem),1fr));
  border-block-start: 1px solid var(--line);
}

.ba-05__services li {
  display: flex;
  align-items: center;
  gap: .45rem;
  font-size: .75rem;
  color: var(--muted);
}

.ba-05__services li b {
  margin-inline-start: auto;
  font-weight: 600;
  font-size: .6875rem;
  color: var(--muted);
}

.ba-05__services span {
  width: .45rem;
  height: .45rem;
  border-radius: 999px;
  flex: none;
  background: var(--green);
}

.ba-05__services li[data-health="warn"] span {
  background: var(--amber);
}

.ba-05__services li[data-health="down"] span {
  background: var(--red);
}

.ba-05__services li[data-health="down"] b {
  color: var(--red);
}

.ba-05__services li[data-health="warn"] b {
  color: var(--amber);
}

.ba-05__page {
  padding: clamp(1.5rem,5vw,3rem) clamp(1rem,4vw,2rem);
  display: grid;
  gap: .6rem;
  align-content: start;
}

.ba-05__page h1 {
  margin: 0;
  font-size: clamp(1.5rem,4cqw,2.25rem);
  font-weight: 600;
  letter-spacing: -.04em;
}

.ba-05__page p {
  margin: 0;
  font-family: var(--mono);
  font-size: .75rem;
  color: var(--muted);
}

.ba-05__cards {
  display: grid;
  gap: .85rem;
  grid-template-columns: repeat(auto-fit,minmax(min(100%,13rem),1fr));
  margin-block-start: 1rem;
}

.ba-05__cards span {
  display: block;
  height: clamp(5rem,12vh,8rem);
  border-radius: 1rem;
  border: 1px solid var(--line);
  background: var(--surface);
}

.ba-05 :focus-visible {
  outline: 2px solid var(--sev,var(--blue));
  outline-offset: 2px;
}

.ba-05__live {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

@container (max-width: 52rem) {
  .ba-05__bar {
    grid-template-columns: 1fr;
  }

  .ba-05__acts {
    justify-content: start;
  }

  .ba-05__switch {
    flex-wrap: wrap;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ba-05 * {
    animation: none !important;
    transition: none !important;
  }
}

@media (forced-colors: active) {
  .ba-05__bar {
    border: 1px solid CanvasText;
  }

  .ba-05__dot {
    background: Highlight;
  }
}
(() => {
  const root = document.querySelector('.ba-05');
  if (!root) return;
  const bar = root.querySelector('[data-bar]');
  const live = root.querySelector('.ba-05__live');
  const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });

  const STATES = {
    operational: { label: 'All systems normal', msg: 'No incidents in the last 7 days. Scheduled maintenance window opens Sunday 02:00 UTC.', health: ['up', 'up', 'up', 'up', 'up'] },
    maintenance: { label: 'Maintenance', msg: 'Planned database upgrade in eu-west-1. Writes pause for up to 4 minutes.', health: ['up', 'warn', 'up', 'warn', 'up'] },
    degraded: { label: 'Degraded', msg: 'Webhook delivery is running 6-9 minutes behind. Retries are queued, nothing is dropped.', health: ['up', 'up', 'up', 'warn', 'up'] },
    major: { label: 'Major outage', msg: 'API writes are failing in eu-west-1. Reads and the dashboard are unaffected.', health: ['up', 'down', 'up', 'warn', 'up'] },
  };

  const apply = (key) => {
    if (bar.dataset.severity === key) return;
    const s = STATES[key];
    bar.dataset.severity = key;
    root.querySelector('[data-label]').textContent = s.label;
    root.querySelector('[data-msg]').textContent = s.msg;
    [...root.querySelectorAll('.ba-05__services li')].forEach((li, i) => {
      li.dataset.health = s.health[i];
      li.querySelector('b').textContent = { up: 'Operational', warn: 'Delayed', down: 'Outage' }[s.health[i]];
    });
    root.querySelectorAll('[data-sev]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.sev === key)));
    live.textContent = 'Status changed to ' + s.label + '. ' + s.msg;
  };

  const stamp = () => {
    const t = root.querySelector('[data-time]');
    const mins = Math.round((Date.now() - new Date(t.dateTime)) / 60000);
    t.textContent = Math.abs(mins) < 60 ? rtf.format(-mins, 'minute') : rtf.format(-Math.round(mins / 60), 'hour');
  };

  root.addEventListener('click', (e) => {
    const sev = e.target.closest('[data-sev]');
    if (sev) apply(sev.dataset.sev);
    const sub = e.target.closest('[data-sub]');
    if (sub) { sub.toggleAttribute('data-on'); sub.textContent = sub.hasAttribute('data-on') ? 'Subscribed' : 'Subscribe'; }
  });
  stamp(); setInterval(stamp, 30000);
})();
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Banners & Alert from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Maintenance and Incident Status Bar
Source: https://codefronts.com/snippets/css-banners-alerts/maintenance-and-incident-status-bar/

A status bar that a real on-call engineer could ship: four severity levels driven entirely by data-severity, a component-scoped update timeline that expands in place, per-service health rows, and an aria-live region that speaks once per state change instead of on every repaint. Change one attribute and the palette, icon, label and pulse all follow.
## HTML
```html
<div class="ba-05">
  <div class="ba-05__bar" data-bar data-severity="major" role="region" aria-labelledby="ba-05-h">
    <div class="ba-05__main">
      <span class="ba-05__dot" aria-hidden="true"></span>
      <div class="ba-05__copy">
        <p class="ba-05__h" id="ba-05-h">
          <span class="ba-05__chip" data-label>Major outage</span>
          <span class="ba-05__msg" data-msg>API writes are failing in eu-west-1. Reads and the dashboard are unaffected.</span>
        </p>
        <p class="ba-05__stamp">
          Updated <time datetime="2026-08-10T09:12:00Z" data-time>09:12 UTC</time>
          <span aria-hidden="true">·</span> Next update by <time datetime="2026-08-10T09:45:00Z">09:45 UTC</time>
        </p>
      </div>
    </div>

    <div class="ba-05__acts">
      <div class="ba-05__switch" role="group" aria-label="Simulate incident severity">
        <button type="button" data-sev="operational">Operational</button>
        <button type="button" data-sev="maintenance">Maintenance</button>
        <button type="button" data-sev="degraded">Degraded</button>
        <button type="button" data-sev="major" aria-pressed="true">Major</button>
      </div>
      <button class="ba-05__sub" type="button" data-sub>Subscribe</button>
    </div>

    <details class="ba-05__log">
      <summary>
        <span>Incident timeline</span>
        <svg viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path d="m4 6.2 4 4 4-4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
      </summary>
      <ol class="ba-05__timeline">
        <li data-phase="monitoring"><p class="ba-05__tHead">Monitoring <time datetime="2026-08-10T09:12:00Z">09:12</time></p><p>Failover to the standby cluster completed. Write latency is back inside SLO; we are watching replication lag.</p></li>
        <li data-phase="identified"><p class="ba-05__tHead">Identified <time datetime="2026-08-10T08:54:00Z">08:54</time></p><p>A schema migration exhausted the connection pool on the primary. Migration rolled back.</p></li>
        <li data-phase="investigating"><p class="ba-05__tHead">Investigating <time datetime="2026-08-10T08:41:00Z">08:41</time></p><p>Elevated 5xx on POST endpoints in eu-west-1. Paging the database on-call.</p></li>
      </ol>
    </details>

    <ul class="ba-05__services">
      <li data-health="up"><span aria-hidden="true"></span>Dashboard<b>Operational</b></li>
      <li data-health="down"><span aria-hidden="true"></span>REST API (writes)<b>Outage</b></li>
      <li data-health="up"><span aria-hidden="true"></span>REST API (reads)<b>Operational</b></li>
      <li data-health="warn"><span aria-hidden="true"></span>Webhooks<b>Delayed</b></li>
      <li data-health="up"><span aria-hidden="true"></span>CDN<b>Operational</b></li>
    </ul>
  </div>

  <main class="ba-05__page">
    <h1>Platform</h1>
    <p>Region eu-west-1 · Plan Scale · 14 projects</p>
    <div class="ba-05__cards" aria-hidden="true"><span></span><span></span><span></span><span></span></div>
  </main>
  <p class="ba-05__live" role="status" aria-live="polite"></p>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap');

.ba-05 {
  --bg: #08090c;
  --surface: #101319;
  --ink: #eef1f6;
  --muted: #98a0ad;
  --line: #20252e;
  --green: #22c55e;
  --amber: #f59e0b;
  --red: #f04438;
  --blue: #3b82f6;
  --sans: "Sora",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono: "JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  container-type: inline-size;
  interpolate-size: allow-keywords;
  position: relative;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr;
  box-sizing: border-box;
  scroll-padding-block-start: 6rem;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
}

.ba-05 *,
.ba-05 *::before,
.ba-05 *::after {
  box-sizing: border-box;
}

@supports (color: oklch(50% 0 0)) {
  .ba-05 {
    --bg: oklch(14% .012 265);
    --surface: oklch(20% .018 265);
    --ink: oklch(96% .006 265);
    --muted: oklch(71% .02 265);
    --line: oklch(29% .022 265);
    --green: oklch(72% .17 150);
    --amber: oklch(78% .16 78);
    --red: oklch(64% .21 26);
    --blue: oklch(65% .18 255);
  }
}

@media (prefers-color-scheme: light) {
  .ba-05:not([data-theme="dark"]) {
    --bg: #f7f8fa;
    --surface: #ffffff;
    --ink: #0d1016;
    --muted: #5b6472;
    --line: #e4e7ec;
  }
}

.ba-05[data-theme="light"] {
  --bg: #f7f8fa;
  --surface: #ffffff;
  --ink: #0d1016;
  --muted: #5b6472;
  --line: #e4e7ec;
}

.ba-05__bar {
  --sev: var(--red);
  --sevSoft: color-mix(in oklab,var(--sev) 13%,var(--surface));
  position: sticky;
  top: 0;
  z-index: 5;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .75rem clamp(1rem,3cqw,2rem);
  padding: clamp(.85rem,2vw,1.15rem) clamp(1rem,4vw,2rem);
  background: var(--sevSoft);
  border-block-end: 1px solid color-mix(in oklab,var(--sev) 30%,var(--line));
  box-shadow: 0 1px 0 0 color-mix(in oklab,var(--sev) 40%,transparent), 0 1.5rem 3rem -2rem rgb(0 0 0 / .6);
  transition: background .5s ease, border-color .5s ease;
}

.ba-05__bar[data-severity="operational"] {
  --sev: var(--green);
}

.ba-05__bar[data-severity="maintenance"] {
  --sev: var(--blue);
}

.ba-05__bar[data-severity="degraded"] {
  --sev: var(--amber);
}

.ba-05__bar[data-severity="major"] {
  --sev: var(--red);
}

.ba-05__main {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .85rem;
  align-items: start;
  min-width: 0;
}

.ba-05__dot {
  position: relative;
  display: block;
  width: .7rem;
  height: .7rem;
  margin: .5rem .25rem;
  border-radius: 999px;
  background: var(--sev);
  box-shadow: 0 0 0 .3rem color-mix(in oklab,var(--sev) 22%,transparent);
  flex: none;
}

.ba-05__dot::before,
.ba-05__dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 999px;
  border: 1.5px solid var(--sev);
  animation: ba-05-ping 2.6s cubic-bezier(.2,.6,.3,1) infinite;
}

.ba-05__dot::after {
  animation-delay: 1.3s;
}

@keyframes ba-05-ping {
  0% {
    scale: 1;
    opacity: .7;
  }

  80%,
    100% {
    scale: 3.4;
    opacity: 0;
  }
}

.ba-05__bar[data-severity="operational"] .ba-05__dot::before,
.ba-05__bar[data-severity="operational"] .ba-05__dot::after {
  animation: none;
  opacity: 0;
}

.ba-05__copy {
  min-width: 0;
  display: grid;
  gap: .3rem;
}

.ba-05__h {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: .6rem;
  flex-wrap: wrap;
  min-width: 0;
}

.ba-05__chip {
  flex: none;
  font-family: var(--mono);
  font-size: .625rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  padding: .28rem .5rem;
  border-radius: .4rem;
  color: var(--sev);
  background: color-mix(in oklab,var(--sev) 16%,transparent);
  border: 1px solid color-mix(in oklab,var(--sev) 34%,transparent);
}

.ba-05__msg {
  font-size: clamp(.8125rem,1.7cqw,.9375rem);
  line-height: 1.5;
  letter-spacing: -.012em;
  max-width: 72ch;
  text-wrap: pretty;
}

.ba-05__stamp {
  margin: 0;
  font-family: var(--mono);
  font-size: .6875rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  display: flex;
  gap: .4rem;
  flex-wrap: wrap;
}

.ba-05__acts {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
  justify-content: end;
}

.ba-05__switch {
  display: flex;
  padding: .2rem;
  gap: .15rem;
  border-radius: .75rem;
  background: color-mix(in oklab,var(--ink) 8%,transparent);
  border: 1px solid var(--line);
}

.ba-05__switch button {
  min-height: 2.25rem;
  padding: .4rem .65rem;
  border: 0;
  border-radius: .6rem;
  cursor: pointer;
  font: inherit;
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--muted);
  background: transparent;
  transition: color .2s ease, background .2s ease;
}

.ba-05__switch button:hover {
  color: var(--ink);
}

.ba-05__switch button[aria-pressed="true"] {
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 1px 0 rgb(255 255 255 / .06) inset;
}

.ba-05__sub {
  min-height: 2.25rem;
  padding: .45rem .9rem;
  border-radius: .7rem;
  cursor: pointer;
  font: inherit;
  font-size: .75rem;
  font-weight: 700;
  color: var(--sev);
  background: transparent;
  border: 1px solid color-mix(in oklab,var(--sev) 40%,transparent);
  transition: background .2s ease;
}

.ba-05__sub:hover {
  background: color-mix(in oklab,var(--sev) 14%,transparent);
}

.ba-05__sub[data-on]::after {
  content: " ✓";
}

.ba-05__log {
  grid-column: 1 / -1;
  border-block-start: 1px solid var(--line);
  margin-block-start: .25rem;
}

.ba-05__log summary {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  min-height: 2.5rem;
  padding: .5rem 0;
  cursor: pointer;
  list-style: none;
  font-size: .75rem;
  font-weight: 600;
  color: var(--muted);
}

.ba-05__log summary::-webkit-details-marker {
  display: none;
}

.ba-05__log summary:hover {
  color: var(--ink);
}

.ba-05__log summary svg {
  width: .9rem;
  height: .9rem;
  transition: rotate .3s ease;
}

.ba-05__log[open] summary svg {
  rotate: 180deg;
}

.ba-05__log::details-content {
  block-size: 0;
  overflow: clip;
  opacity: 0;
  transition: block-size .4s cubic-bezier(.16,1,.3,1), opacity .3s ease, content-visibility .4s allow-discrete;
}

.ba-05__log[open]::details-content {
  block-size: auto;
  opacity: 1;
}

.ba-05__timeline {
  margin: 0 0 .75rem;
  padding: 0 0 0 1.1rem;
  list-style: none;
  display: grid;
  gap: .85rem;
  max-block-size: 14rem;
  overflow: auto;
  overscroll-behavior: contain;
  border-inline-start: 2px solid color-mix(in oklab,var(--sev) 35%,var(--line));
}

.ba-05__timeline li {
  position: relative;
  padding-inline-start: .35rem;
}

.ba-05__timeline li::before {
  content: "";
  position: absolute;
  inset-inline-start: -1.55rem;
  inset-block-start: .35rem;
  width: .55rem;
  height: .55rem;
  border-radius: 999px;
  background: var(--sev);
  box-shadow: 0 0 0 3px var(--sevSoft);
}

.ba-05__timeline li[data-phase="investigating"]::before {
  background: var(--muted);
}

.ba-05__tHead {
  margin: 0 0 .2rem;
  display: flex;
  gap: .5rem;
  align-items: baseline;
  font-family: var(--mono);
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--sev);
}

.ba-05__tHead time {
  color: var(--muted);
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

.ba-05__timeline p:last-child {
  margin: 0;
  font-size: .8125rem;
  line-height: 1.55;
  color: var(--muted);
  max-width: 70ch;
  text-wrap: pretty;
}

.ba-05__services {
  grid-column: 1 / -1;
  margin: 0;
  padding: .65rem 0 0;
  list-style: none;
  display: grid;
  gap: .35rem .9rem;
  grid-template-columns: repeat(auto-fit,minmax(min(100%,11rem),1fr));
  border-block-start: 1px solid var(--line);
}

.ba-05__services li {
  display: flex;
  align-items: center;
  gap: .45rem;
  font-size: .75rem;
  color: var(--muted);
}

.ba-05__services li b {
  margin-inline-start: auto;
  font-weight: 600;
  font-size: .6875rem;
  color: var(--muted);
}

.ba-05__services span {
  width: .45rem;
  height: .45rem;
  border-radius: 999px;
  flex: none;
  background: var(--green);
}

.ba-05__services li[data-health="warn"] span {
  background: var(--amber);
}

.ba-05__services li[data-health="down"] span {
  background: var(--red);
}

.ba-05__services li[data-health="down"] b {
  color: var(--red);
}

.ba-05__services li[data-health="warn"] b {
  color: var(--amber);
}

.ba-05__page {
  padding: clamp(1.5rem,5vw,3rem) clamp(1rem,4vw,2rem);
  display: grid;
  gap: .6rem;
  align-content: start;
}

.ba-05__page h1 {
  margin: 0;
  font-size: clamp(1.5rem,4cqw,2.25rem);
  font-weight: 600;
  letter-spacing: -.04em;
}

.ba-05__page p {
  margin: 0;
  font-family: var(--mono);
  font-size: .75rem;
  color: var(--muted);
}

.ba-05__cards {
  display: grid;
  gap: .85rem;
  grid-template-columns: repeat(auto-fit,minmax(min(100%,13rem),1fr));
  margin-block-start: 1rem;
}

.ba-05__cards span {
  display: block;
  height: clamp(5rem,12vh,8rem);
  border-radius: 1rem;
  border: 1px solid var(--line);
  background: var(--surface);
}

.ba-05 :focus-visible {
  outline: 2px solid var(--sev,var(--blue));
  outline-offset: 2px;
}

.ba-05__live {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

@container (max-width: 52rem) {
  .ba-05__bar {
    grid-template-columns: 1fr;
  }

  .ba-05__acts {
    justify-content: start;
  }

  .ba-05__switch {
    flex-wrap: wrap;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ba-05 * {
    animation: none !important;
    transition: none !important;
  }
}

@media (forced-colors: active) {
  .ba-05__bar {
    border: 1px solid CanvasText;
  }

  .ba-05__dot {
    background: Highlight;
  }
}
```

## JavaScript
```js
(() => {
  const root = document.querySelector('.ba-05');
  if (!root) return;
  const bar = root.querySelector('[data-bar]');
  const live = root.querySelector('.ba-05__live');
  const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });

  const STATES = {
    operational: { label: 'All systems normal', msg: 'No incidents in the last 7 days. Scheduled maintenance window opens Sunday 02:00 UTC.', health: ['up', 'up', 'up', 'up', 'up'] },
    maintenance: { label: 'Maintenance', msg: 'Planned database upgrade in eu-west-1. Writes pause for up to 4 minutes.', health: ['up', 'warn', 'up', 'warn', 'up'] },
    degraded: { label: 'Degraded', msg: 'Webhook delivery is running 6-9 minutes behind. Retries are queued, nothing is dropped.', health: ['up', 'up', 'up', 'warn', 'up'] },
    major: { label: 'Major outage', msg: 'API writes are failing in eu-west-1. Reads and the dashboard are unaffected.', health: ['up', 'down', 'up', 'warn', 'up'] },
  };

  const apply = (key) => {
    if (bar.dataset.severity === key) return;
    const s = STATES[key];
    bar.dataset.severity = key;
    root.querySelector('[data-label]').textContent = s.label;
    root.querySelector('[data-msg]').textContent = s.msg;
    [...root.querySelectorAll('.ba-05__services li')].forEach((li, i) => {
      li.dataset.health = s.health[i];
      li.querySelector('b').textContent = { up: 'Operational', warn: 'Delayed', down: 'Outage' }[s.health[i]];
    });
    root.querySelectorAll('[data-sev]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.sev === key)));
    live.textContent = 'Status changed to ' + s.label + '. ' + s.msg;
  };

  const stamp = () => {
    const t = root.querySelector('[data-time]');
    const mins = Math.round((Date.now() - new Date(t.dateTime)) / 60000);
    t.textContent = Math.abs(mins) < 60 ? rtf.format(-mins, 'minute') : rtf.format(-Math.round(mins / 60), 'hour');
  };

  root.addEventListener('click', (e) => {
    const sev = e.target.closest('[data-sev]');
    if (sev) apply(sev.dataset.sev);
    const sub = e.target.closest('[data-sub]');
    if (sub) { sub.toggleAttribute('data-on'); sub.textContent = sub.hasAttribute('data-on') ? 'Subscribed' : 'Subscribe'; }
  });
  stamp(); setInterval(stamp, 30000);
})();
```

How this works

Severity is one attribute and one token block, so adding a fifth level is copy-paste:

.ba-05__bar[data-severity="major"]{ --sev:var(--red); --sevBg:color-mix(in oklab,var(--red) 14%,var(--surface)); }
.ba-05__bar[data-severity="maintenance"]{ --sev:var(--blue); }

Everything downstream reads --sev: the dot, the label chip, the border, the timeline spine, the focus ring. The pulse is two pseudo-elements on the dot with staggered animation-delay, and it is decorative — the state is always in text and in the aria-label.

.ba-05__dot::before,.ba-05__dot::after{ animation: ba-05-ping 2.6s cubic-bezier(.2,.6,.3,1) infinite; }
.ba-05__dot::after{ animation-delay:1.3s; }

The status region is written only when the severity actually changes, which is the difference between a helpful announcement and an unusable page. Timestamps are real <time> elements, so "12 min ago" is generated from datetime and stays correct if the page sits open.

Make it yours

  • Wire it to a real status feed: replace the STATES array with a poll of your status API and call apply(state).
  • Hide the service rows for a marketing site and keep only the headline row — the grid collapses cleanly.
  • Add your own severity by copying a [data-severity] block and a matching icon; nothing else needs to know about it.
  • Set --bar-position:sticky or fixed depending on whether the bar should scroll away.
  • The subscribe button is a hook for your incident mailing list or webhook — it is a plain button, not a form, so it drops into any framework.

Gotchas — read before shipping

  • Colour alone must never carry severity: every state here also changes the icon and the label text, which is WCAG 1.4.1.
  • aria-live="assertive" interrupts the user mid-sentence — reserve it for outages that block work, and use polite for everything else.
  • Do not re-render the whole bar on each poll; write only the changed nodes or screen readers will re-announce unchanged content.
  • A position:fixed status bar must be accounted for in scroll padding: scroll-padding-block-start on the root keeps anchor targets from hiding underneath it.
  • Intl.RelativeTimeFormat needs the difference in the right unit — feeding it seconds for a two-day-old incident produces "172800 seconds ago".
  • If the incident log can grow long, cap it with max-block-size and let it scroll; an unbounded timeline pushes your page content off-screen on mobile.

Browser support

ChromeSafariFirefoxEdge
131+18.2+129+131+

The bar, severity theming, pulse and ARIA work in every modern browser. ::details-content (Chrome 131, Safari 18.2) and interpolate-size only smooth the log expansion; :has() needs Chrome 105 / Safari 15.4 / Firefox 121 and only drives the automatic page offset.

Techniques used in this demo

Search CodeFronts

Loading…