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.
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
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.
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.
No. The breathing is CSS keyframes. React is used for the state switch and for pausing the animation when the panel is off screen.
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.
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.
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.