Breathing Glow Border Status Panel

A dark system status panel whose border glows and fades like slow breathing. The glow is three layers: a sharp coloured edge, a wide halo and a faint ring that drifts outward on each breath. Switch between operational, degraded and outage and the colour moves from green to amber to red while the breath gets faster. Inside are four services with uptime bars that grow in on load. On phones each service puts its bars on a second line.

Code

import { useEffect, useRef, useState } from "react";
import type { CSSProperties } from "react";

type Health = "ok" | "slow" | "down";

// Each state sets the glow colour (as an RGB triplet) and how long one breath takes.
const states = [
  { key: "operational", name: "Operational", headline: "All systems operational", note: "Last incident 41 days ago", rgb: "52 211 153", seconds: 3.2, health: ["ok", "ok", "ok", "ok"] as Health[] },
  { key: "degraded", name: "Degraded", headline: "Webhooks are running slow", note: "Deliveries delayed by about 90 seconds", rgb: "251 191 36", seconds: 1.9, health: ["ok", "ok", "slow", "ok"] as Health[] },
  { key: "outage", name: "Outage", headline: "Payments API is down", note: "Engineers paged at 14:02 UTC", rgb: "251 113 133", seconds: 1.1, health: ["down", "ok", "slow", "ok"] as Health[] },
];

const services = [
  { name: "Payments API", region: "Frankfurt, Virginia", uptime: "99.99%" },
  { name: "Dashboard", region: "Global edge", uptime: "100%" },
  { name: "Webhooks", region: "Frankfurt", uptime: "99.94%" },
  { name: "Payout batches", region: "Dublin", uptime: "99.98%" },
];

const BARS = 34;
const barColour: Record<Health, string> = { ok: "bg-emerald-400", slow: "bg-amber-400", down: "bg-rose-500" };
const healthText: Record<Health, string> = { ok: "text-emerald-300", slow: "text-amber-300", down: "text-rose-300" };
const healthLabel: Record<Health, string> = { ok: "Healthy", slow: "Slow", down: "Down" };

export default function BreathingGlowBorderStatusPanel() {
  const rootRef = useRef<HTMLElement>(null);
  const [paused, setPaused] = useState(false);
  const [active, setActive] = useState(0);
  const state = states[active];

  // Stop the breathing while the section is scrolled out of view.
  useEffect(() => {
    const el = rootRef.current;
    if (!el || typeof IntersectionObserver === "undefined") return;
    const observer = new IntersectionObserver(([entry]) => setPaused(!entry.isIntersecting));
    observer.observe(el);
    return () => observer.disconnect();
  }, []);

  return (
    <section
      ref={rootRef}
      data-paused={paused}
      style={{ "--bgb-rgb": state.rgb, "--bgb-breath": `${state.seconds}s` } as CSSProperties}
      className="breathing-glow-border-status-panel relative overflow-hidden bg-neutral-950 px-4 py-16 text-white sm:px-8 sm:py-20"
    >
      <style>{`
        @keyframes breathing-glow-border-status-panel-edge {
          0%, 100% { opacity: 0.35; }
          50% { opacity: 1; }
        }
        @keyframes breathing-glow-border-status-panel-halo {
          0%, 100% { opacity: 0.12; transform: scale(0.985); }
          50% { opacity: 0.75; transform: scale(1.012); }
        }
        @keyframes breathing-glow-border-status-panel-wave {
          0%, 45% { opacity: 0; transform: scale(1); }
          50% { opacity: 0.55; transform: scale(1); }
          100% { opacity: 0; transform: scale(1.07, 1.12); }
        }
        @keyframes breathing-glow-border-status-panel-dot {
          0%, 100% { transform: scale(1); opacity: 0.7; }
          50% { transform: scale(2.6); opacity: 0; }
        }
        @keyframes breathing-glow-border-status-panel-bar {
          from { transform: scaleY(0.15); opacity: 0; }
          to { transform: scaleY(1); }
        }
        @keyframes breathing-glow-border-status-panel-rise {
          from { opacity: 0; transform: translateY(20px); }
          to { opacity: 1; transform: translateY(0); }
        }
        .breathing-glow-border-status-panel .bgb-rise {
          animation: breathing-glow-border-status-panel-rise 0.8s cubic-bezier(0.22, 1, 0.36, 1) both;
        }
        .breathing-glow-border-status-panel .bgb-edge {
          border: 1px solid rgb(var(--bgb-rgb));
          box-shadow: 0 0 14px 0 rgb(var(--bgb-rgb) / 0.7), inset 0 0 14px 0 rgb(var(--bgb-rgb) / 0.22);
          animation: breathing-glow-border-status-panel-edge var(--bgb-breath) ease-in-out infinite;
          transition: border-color 0.5s, box-shadow 0.5s;
        }
        .breathing-glow-border-status-panel .bgb-halo {
          box-shadow: 0 0 70px 6px rgb(var(--bgb-rgb) / 0.6), 0 0 160px 30px rgb(var(--bgb-rgb) / 0.3);
          animation: breathing-glow-border-status-panel-halo var(--bgb-breath) ease-in-out infinite;
          transition: box-shadow 0.5s;
          will-change: transform, opacity;
        }
        .breathing-glow-border-status-panel .bgb-wave {
          border: 1px solid rgb(var(--bgb-rgb));
          animation: breathing-glow-border-status-panel-wave var(--bgb-breath) ease-out infinite;
          will-change: transform, opacity;
        }
        .breathing-glow-border-status-panel .bgb-dot {
          background: rgb(var(--bgb-rgb));
          transition: background-color 0.5s;
        }
        .breathing-glow-border-status-panel .bgb-dot-ping {
          background: rgb(var(--bgb-rgb));
          animation: breathing-glow-border-status-panel-dot var(--bgb-breath) ease-out infinite;
        }
        .breathing-glow-border-status-panel .bgb-pool {
          background: radial-gradient(closest-side, rgb(var(--bgb-rgb) / 0.22), rgb(var(--bgb-rgb) / 0));
          animation: breathing-glow-border-status-panel-edge var(--bgb-breath) ease-in-out infinite;
        }
        .breathing-glow-border-status-panel .bgb-bar {
          transform-origin: bottom;
          animation: breathing-glow-border-status-panel-bar 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
        }
        .breathing-glow-border-status-panel[data-paused=true] * {
          animation-play-state: paused;
        }
        @media (prefers-reduced-motion: reduce) {
          .breathing-glow-border-status-panel .bgb-rise,
          .breathing-glow-border-status-panel .bgb-bar,
          .breathing-glow-border-status-panel .bgb-wave,
          .breathing-glow-border-status-panel .bgb-dot-ping { animation: none; }
          .breathing-glow-border-status-panel .bgb-wave,
          .breathing-glow-border-status-panel .bgb-dot-ping { opacity: 0; }
          .breathing-glow-border-status-panel .bgb-edge,
          .breathing-glow-border-status-panel .bgb-pool { animation: none; opacity: 1; }
          .breathing-glow-border-status-panel .bgb-halo { animation: none; opacity: 0.5; }
        }
      `}</style>

      {/* Light the panel throws on the wall behind it */}
      <div aria-hidden="true" className="bgb-pool pointer-events-none absolute left-1/2 top-1/2 h-full w-full max-w-4xl -translate-x-1/2 -translate-y-1/2" />

      <div className="bgb-rise relative mx-auto w-full max-w-2xl">
        {/* Three layers make the breath: a wide halo, a sharp edge and a ring that drifts outward */}
        <div aria-hidden="true" className="bgb-halo pointer-events-none absolute inset-0 rounded-3xl" />
        <div aria-hidden="true" className="bgb-wave pointer-events-none absolute inset-0 rounded-3xl" />

        <div className="relative rounded-3xl bg-neutral-900">
          <div aria-hidden="true" className="bgb-edge pointer-events-none absolute inset-0 rounded-3xl" />
          <div aria-hidden="true" className="pointer-events-none absolute inset-0 rounded-3xl border border-white/10" />

          <div className="relative p-5 sm:p-8">
            <div className="flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between">
              <div className="flex items-start gap-4">
                <span className="relative mt-1.5 flex h-3 w-3 shrink-0">
                  <span aria-hidden="true" className="bgb-dot-ping absolute inset-0 rounded-full" />
                  <span className="bgb-dot relative h-3 w-3 rounded-full" />
                </span>
                <div aria-live="polite">
                  <p className="text-xs font-semibold uppercase tracking-widest text-neutral-400">Ledgerline status</p>
                  <h2 className="mt-1 text-2xl font-semibold tracking-tight text-white">{state.headline}</h2>
                  <p className="mt-1 text-sm text-neutral-300">{state.note}</p>
                </div>
              </div>

              <div role="group" aria-label="Preview a status" className="inline-flex shrink-0 self-start rounded-full border border-white/10 bg-black/40 p-1">
                {states.map((option, index) => (
                  <button
                    key={option.key}
                    type="button"
                    aria-pressed={active === index}
                    onClick={() => setActive(index)}
                    className={`h-10 rounded-full px-3 text-xs font-semibold transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-white sm:px-4 ${
                      active === index ? "bg-white text-neutral-950" : "text-neutral-300 hover:text-white"
                    }`}
                  >
                    {option.name}
                  </button>
                ))}
              </div>
            </div>

            <ul className="mt-8 divide-y divide-white/10 border-t border-white/10">
              {services.map((service, row) => {
                const health = state.health[row];
                // The newest bars (right-hand side) take the colour of the current problem.
                const troubled = health === "down" ? 6 : health === "slow" ? 4 : 0;
                return (
                  <li key={service.name} className="grid grid-cols-2 items-center gap-x-4 gap-y-3 py-4 sm:grid-cols-12">
                    <div className="sm:col-span-4">
                      <p className="text-sm font-semibold text-white">{service.name}</p>
                      <p className="mt-0.5 text-xs text-neutral-400">{service.region}</p>
                    </div>
                    <div className="text-right sm:order-last sm:col-span-2">
                      <p className={`text-sm font-semibold transition-colors duration-500 ${healthText[health]}`}>{healthLabel[health]}</p>
                      <p className="mt-0.5 font-mono text-xs text-neutral-400">{service.uptime}</p>
                    </div>
                    <div aria-hidden="true" className="col-span-2 flex h-8 items-end gap-0.5 sm:col-span-6">
                      {Array.from({ length: BARS }, (_, i) => {
                        const tone = i >= BARS - troubled ? barColour[health] : (i * 7 + row * 5) % 23 === 0 ? "bg-amber-400" : "bg-emerald-400";
                        return (
                          <span
                            key={i}
                            style={{ animationDelay: `${0.25 + row * 0.08 + i * 0.014}s` }}
                            className={`bgb-bar h-full flex-1 rounded-sm transition-colors duration-500 ${tone} ${i >= BARS - troubled ? "opacity-100" : "opacity-70"}`}
                          />
                        );
                      })}
                    </div>
                  </li>
                );
              })}
            </ul>

            <div className="flex flex-col gap-4 border-t border-white/10 pt-5 sm:flex-row sm:items-center sm:justify-between">
              <p className="text-xs text-neutral-400">Checked every 30 seconds from 11 locations. Bars show the last 34 days.</p>
              <a
                href="#"
                className="inline-flex h-10 shrink-0 items-center justify-center rounded-full border border-white/15 px-4 text-sm font-semibold text-white transition-colors hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white"
              >
                Get incident alerts
              </a>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/breathing-glow-border-status-panel.json

When to use it

How to customize it

Accessibility

Questions

How do I animate a glowing border without lag?

Do not animate `box-shadow` itself, because the browser repaints the shadow on every frame. Here the shadow is drawn once on an empty layer over the panel and only that layer's `opacity` and `transform` are animated, which is cheap.

How does the colour change between states?

The section sets two CSS variables, `--bgb-rgb` and `--bgb-breath`, from the active state. Every glow layer reads them with `rgb(var(--bgb-rgb) / alpha)`, so one value recolours the edge, halo, ring, dot and background light.

Does it need framer-motion?

No. The breathing is CSS keyframes. React is used for the state switch and for pausing the animation when the panel is off screen.

Can I use the glow on a different block?

Yes. Copy the style block and the three empty layers (`bgb-halo`, `bgb-wave`, `bgb-edge`) into any relatively positioned box with the same border radius, and set the two variables on a parent.

How do I connect it to real data?

Replace the `active` state with the value from your monitoring feed and map each service's result to `ok`, `slow` or `down` in place of the `health` arrays.

Related: Animated Gradient Border Card Wrapper, Button with Rotating Gradient Glow Border, Stat Tiles with Sparkline Trends.

More borders and glows or all free UI components. Start for free to have a full site built around it.

More in Borders

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap