<div><span class="lbl">Glow band with gradient numbers</span>
<section class="dk dk-band" aria-labelledby="dk-band-title">
<h3 id="dk-band-title">Relay runs at the edge, everywhere</h3>
<p>One network for APIs, static sites and functions, measured across every request in September 2026.</p>
<ul>
<li><p class="dk-num"><span data-count>2.1</span><span class="dk-unit">B</span></p><p>requests served a day</p></li>
<li><p class="dk-num"><span data-count>99.99</span><span class="dk-unit">%</span></p><p>uptime over the last 12 months</p></li>
<li><p class="dk-num"><span data-count>38</span><span class="dk-unit">ms</span></p><p>median response time worldwide</p></li>
<li><p class="dk-num"><span data-count>312</span></p><p>cities with an edge node</p></li>
</ul>
</section></div>
<div><span class="lbl">Bordered grid with monospace labels</span>
<ul class="dk dk-grid" aria-label="Relay developer platform">
<li><span class="dk-key">developers</span><p class="dk-num"><span data-count>41,200</span></p><p><span class="dk-up">Up 18%</span> since June</p></li>
<li><span class="dk-key">deploys / month</span><p class="dk-num"><span data-count>1.3</span><span class="dk-unit">M</span></p><p>Median build time 42 seconds</p></li>
<li><span class="dk-key">cold start</span><p class="dk-num"><span data-count>9</span><span class="dk-unit">ms</span></p><p>p50 for functions under 1 MB</p></li>
<li><span class="dk-key">regions</span><p class="dk-num"><span data-count>24</span></p><p>Pick one or run in all of them</p></li>
<li><span class="dk-key">open-source repos</span><p class="dk-num"><span data-count>6,800</span></p><p>Templates and starters on GitHub</p></li>
<li><span class="dk-key">survey score</span><p class="dk-num"><span data-count>4.8</span><span class="dk-unit">/5</span></p><p>From 2,900 developers in August</p></li>
</ul></div>
<div><span class="lbl">Spotlight figure</span>
<section class="dk dk-spot" aria-labelledby="dk-spot-title">
<p class="dk-key">Relay Pay · 2026 so far</p>
<p class="dk-num"><span class="dk-unit">$</span><span data-count>1.6</span><span class="dk-unit">B</span></p>
<h3 id="dk-spot-title">processed for 9,300 online stores</h3>
<ul>
<li><strong>0.04%</strong> fraud rate on card payments</li>
<li><strong>135</strong> currencies accepted</li>
<li><strong>2 days</strong> standard payout time</li>
</ul>
</section></div>
CSS
/* Dark stats sections. Numbers count up on view (see the script) and are real text in the HTML.
Gradient numbers fall back to a solid light colour where background-clip:text is not supported. */
.dk{--dk-bg:#0f121b;--dk-card:#131725;--dk-line:#262c3f;--dk-ink:#eef1fa;--dk-muted:#a3abc2;--dk-cyan:#67e8f9;--dk-violet:#c4b5fd;
--dk-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--dk-ink)}
.dk-num{font-weight:800;letter-spacing:-.035em;line-height:1;font-variant-numeric:tabular-nums;white-space:nowrap}
.dk-unit{font-size:.5em;letter-spacing:0;margin-left:.1em;color:var(--dk-muted)}
/* 1. Glow band with gradient numbers */
.dk-band{position:relative;overflow:hidden;border:1px solid var(--dk-line);border-radius:26px;padding:clamp(28px,5vw,52px) clamp(20px,4vw,44px);
background:radial-gradient(60% 90% at 50% 0%,rgba(103,232,249,.14),transparent 70%),radial-gradient(50% 80% at 100% 100%,rgba(167,139,250,.16),transparent 70%),var(--dk-bg);
text-align:center}
.dk-band h3{font-size:clamp(1.3rem,3vw,1.8rem);letter-spacing:-.02em}
.dk-band > p{margin:8px auto 0;max-width:52ch;color:var(--dk-muted)}
.dk-band ul{list-style:none;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:24px 16px;margin-top:34px}
.dk-band .dk-num{font-size:clamp(2.4rem,5vw,3.4rem);color:var(--dk-ink)}
@supports ((-webkit-background-clip:text) or (background-clip:text)){
.dk-band .dk-num{background:linear-gradient(120deg,var(--dk-cyan),var(--dk-violet));-webkit-background-clip:text;background-clip:text;color:transparent}
.dk-band .dk-unit{color:inherit}
}
.dk-band li p:last-child{margin-top:8px;font-size:.9rem;color:var(--dk-muted)}
/* 2. Bordered grid with monospace labels */
.dk-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;background:var(--dk-line);border:1px solid var(--dk-line);border-radius:20px;overflow:hidden;list-style:none}
.dk-grid li{background:var(--dk-card);padding:24px 24px 22px;display:flex;flex-direction:column;gap:10px}
.dk-key{font:600 .74rem/1.2 var(--dk-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--dk-cyan)}
.dk-grid .dk-num{font-size:clamp(2rem,4vw,2.6rem)}
.dk-grid li p:last-child{font-size:.86rem;color:var(--dk-muted)}
.dk-up{color:#86efac;font-weight:600}
/* 3. Spotlight figure */
.dk-spot{position:relative;border:1px solid var(--dk-line);border-radius:28px;overflow:hidden;text-align:center;
padding:clamp(34px,6vw,64px) clamp(20px,4vw,48px) clamp(26px,4vw,40px);
background:radial-gradient(45% 65% at 50% 38%,rgba(196,181,253,.22),transparent 72%),linear-gradient(#11141f,#0b0d15)}
.dk-spot .dk-key{color:var(--dk-violet)}
.dk-spot .dk-num{margin-top:14px;font-size:clamp(4.2rem,14vw,8rem);color:#fff;text-shadow:0 0 40px rgba(196,181,253,.35)}
.dk-spot .dk-num .dk-unit{color:var(--dk-violet)}
.dk-spot h3{margin-top:10px;font-size:1.1rem;font-weight:600;color:#dfe3f1}
.dk-spot ul{list-style:none;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;margin-top:30px;padding-top:22px;border-top:1px solid var(--dk-line)}
.dk-spot li{font-size:.84rem;color:var(--dk-muted)}
.dk-spot li strong{display:block;font-size:1.35rem;color:var(--dk-ink);font-variant-numeric:tabular-nums}
@media (max-width:820px){.dk-band ul{grid-template-columns:repeat(2,minmax(0,1fr))}.dk-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:520px){.dk-grid,.dk-spot ul{grid-template-columns:1fr}}
@media (prefers-reduced-motion:reduce){.dk *{transition:none;animation:none}}
JavaScript
// Count-up on view. The text inside each [data-count] element is the real, final number, so it shows as-is
// without JavaScript and under prefers-reduced-motion (no count from 0). While the digits run, an invisible copy
// of the final number keeps the width steady (digits grow from the right, next to any unit) and a visually
// hidden copy is what screen readers get.
// Optional: data-from="…" (start value, default 0) and data-duration="…" (ms, default 1800).
(() => {
const els = document.querySelectorAll('[data-count]');
if (!els.length || !('IntersectionObserver' in window) || matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const lang = document.documentElement.lang || undefined;
const sep = type => new Intl.NumberFormat(lang).formatToParts(12345.6).find(p => p.type === type)?.value;
const group = sep('group') || ',', point = sep('decimal') || '.';
const esc = s => s.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
const numRe = new RegExp(`^(\\D*?)(\\d[\\d${esc(group)}]*(?:${esc(point)}\\d+)?)(.*)$`, 's');
const ease = t => 1 - (1 - t) ** 4;
const span = (css, text) => { const s = document.createElement('span'); s.style.cssText = css; s.textContent = text; return s; };
const jobs = new WeakMap();
const run = el => {
const j = jobs.get(el), t0 = performance.now();
const tick = now => {
const p = Math.min((now - t0) / j.dur, 1);
if (p < 1) { j.live.textContent = j.pre + j.fmt.format(j.from + (j.to - j.from) * ease(p)) + j.post; requestAnimationFrame(tick); }
else { el.textContent = j.final; el.style.display = ''; }
};
requestAnimationFrame(tick);
};
const io = new IntersectionObserver(list => list.forEach(e => {
if (e.isIntersecting) { io.unobserve(e.target); run(e.target); }
}), { threshold: 0.6 });
els.forEach(el => {
const final = el.textContent, m = final.trim().match(numRe);
if (!m) return;
const [, pre, num, post] = m, dec = num.includes(point) ? num.split(point)[1].length : 0;
const to = parseFloat(num.split(group).join('').replace(point, '.')), from = parseFloat(el.dataset.from) || 0;
const fmt = new Intl.NumberFormat(lang, { minimumFractionDigits: dec, maximumFractionDigits: dec, useGrouping: num.includes(group) });
const live = span('grid-area:1/1;justify-self:end', pre + fmt.format(from) + post);
live.setAttribute('aria-hidden', 'true');
el.style.display = 'inline-grid';
el.replaceChildren(span('grid-area:1/1;visibility:hidden', final.trim()), live,
span('position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap', final.trim()));
jobs.set(el, { final, pre, post, to, from, fmt, live, dur: +el.dataset.duration || 1800 });
io.observe(el);
});
})();
Bootstrap 5 code
Requires: Bootstrap 5.3.8 CSS
HTML
<div><span class="lbl">Glow band with gradient numbers</span>
<section class="dk dk-band text-center" aria-labelledby="dk-band-title">
<h3 class="fw-bold mb-0" id="dk-band-title">Relay runs at the edge, everywhere</h3>
<p class="text-body-secondary mx-auto mt-2" style="max-width:52ch">One network for APIs, static sites and functions, measured across every request in September 2026.</p>
<ul class="list-unstyled row row-cols-2 row-cols-md-4 gx-3 gy-4 mt-3 mb-0">
<li class="col"><p class="dk-num"><span data-count>2.1</span><span class="dk-unit">B</span></p><p class="small text-body-secondary mt-2">requests served a day</p></li>
<li class="col"><p class="dk-num"><span data-count>99.99</span><span class="dk-unit">%</span></p><p class="small text-body-secondary mt-2">uptime over the last 12 months</p></li>
<li class="col"><p class="dk-num"><span data-count>38</span><span class="dk-unit">ms</span></p><p class="small text-body-secondary mt-2">median response time worldwide</p></li>
<li class="col"><p class="dk-num"><span data-count>312</span></p><p class="small text-body-secondary mt-2">cities with an edge node</p></li>
</ul>
</section></div>
<div><span class="lbl">Bordered grid with monospace labels</span>
<div class="dk dk-grid"><ul class="list-unstyled row row-cols-1 row-cols-sm-2 row-cols-md-3 g-0 mb-0" aria-label="Relay developer platform">
<li class="col d-flex flex-column gap-2"><span class="dk-key">developers</span><p class="dk-num"><span data-count>41,200</span></p><p class="small text-body-secondary"><span class="dk-up">Up 18%</span> since June</p></li>
<li class="col d-flex flex-column gap-2"><span class="dk-key">deploys / month</span><p class="dk-num"><span data-count>1.3</span><span class="dk-unit">M</span></p><p class="small text-body-secondary">Median build time 42 seconds</p></li>
<li class="col d-flex flex-column gap-2"><span class="dk-key">cold start</span><p class="dk-num"><span data-count>9</span><span class="dk-unit">ms</span></p><p class="small text-body-secondary">p50 for functions under 1 MB</p></li>
<li class="col d-flex flex-column gap-2"><span class="dk-key">regions</span><p class="dk-num"><span data-count>24</span></p><p class="small text-body-secondary">Pick one or run in all of them</p></li>
<li class="col d-flex flex-column gap-2"><span class="dk-key">open-source repos</span><p class="dk-num"><span data-count>6,800</span></p><p class="small text-body-secondary">Templates and starters on GitHub</p></li>
<li class="col d-flex flex-column gap-2"><span class="dk-key">survey score</span><p class="dk-num"><span data-count>4.8</span><span class="dk-unit">/5</span></p><p class="small text-body-secondary">From 2,900 developers in August</p></li>
</ul></div></div>
<div><span class="lbl">Spotlight figure</span>
<section class="dk dk-spot text-center" aria-labelledby="dk-spot-title">
<p class="dk-key">Relay Pay · 2026 so far</p>
<p class="dk-num mt-3"><span class="dk-unit">$</span><span data-count>1.6</span><span class="dk-unit">B</span></p>
<h3 class="fw-semibold mt-2 mb-0" id="dk-spot-title" style="font-size:1.1rem">processed for 9,300 online stores</h3>
<ul class="list-unstyled row row-cols-1 row-cols-sm-3 g-3 border-top mt-4 pt-3 mb-0 small text-body-secondary">
<li class="col"><strong>0.04%</strong> fraud rate on card payments</li>
<li class="col"><strong>135</strong> currencies accepted</li>
<li class="col"><strong>2 days</strong> standard payout time</li>
</ul>
</section></div>
CSS
/* Dark stats on Bootstrap's dark colour mode (data-bs-theme="dark"): grid rows and utilities for layout,
the theme variables retuned for a blue-black ground, and a few rules for the glow and gradient numbers. */
.dk{--dk-line:#262c3f;--dk-cyan:#67e8f9;--dk-violet:#c4b5fd;--bs-body-color:#eef1fa;--bs-secondary-color:#a3abc2;--bs-border-color:#262c3f;color:#eef1fa}
.dk p{margin:0}
.dk-num{font-weight:800;letter-spacing:-.035em;line-height:1;font-variant-numeric:tabular-nums;white-space:nowrap}
.dk-unit{font-size:.5em;letter-spacing:0;margin-left:.1em;color:#a3abc2}
.dk-key{font:600 .74rem/1.2 var(--bs-font-monospace);letter-spacing:.08em;text-transform:uppercase;color:var(--dk-cyan)}
.dk-band{border:1px solid var(--dk-line);border-radius:26px;padding:clamp(28px,5vw,52px) clamp(20px,4vw,44px);
background:radial-gradient(60% 90% at 50% 0%,rgba(103,232,249,.14),transparent 70%),radial-gradient(50% 80% at 100% 100%,rgba(167,139,250,.16),transparent 70%),#0f121b}
.dk-band h3{font-size:clamp(1.3rem,3vw,1.8rem);letter-spacing:-.02em}
.dk-band .dk-num{font-size:clamp(2.4rem,5vw,3.4rem)}
@supports ((-webkit-background-clip:text) or (background-clip:text)){
.dk-band .dk-num{background:linear-gradient(120deg,var(--dk-cyan),var(--dk-violet));-webkit-background-clip:text;background-clip:text;color:transparent}
.dk-band .dk-unit{color:inherit}
}
.dk-grid{border:1px solid var(--dk-line);border-radius:20px;overflow:hidden;background:var(--dk-line)}
.dk-grid .col{background:#131725;padding:24px 24px 22px;box-shadow:-1px 0 0 var(--dk-line),0 -1px 0 var(--dk-line)}
.dk-grid .dk-num{font-size:clamp(2rem,4vw,2.6rem)}
.dk-up{color:#86efac;font-weight:600}
.dk-spot{border:1px solid var(--dk-line);border-radius:28px;padding:clamp(34px,6vw,64px) clamp(20px,4vw,48px) clamp(26px,4vw,40px);
background:radial-gradient(45% 65% at 50% 38%,rgba(196,181,253,.22),transparent 72%),linear-gradient(#11141f,#0b0d15)}
.dk-spot .dk-key{color:var(--dk-violet)}
.dk-spot .dk-num{font-size:clamp(4.2rem,14vw,8rem);color:#fff;text-shadow:0 0 40px rgba(196,181,253,.35)}
.dk-spot .dk-num .dk-unit{color:var(--dk-violet)}
.dk-spot h3{color:#dfe3f1}
.dk-spot li strong{display:block;font-size:1.35rem;color:#eef1fa;font-variant-numeric:tabular-nums}
@media (prefers-reduced-motion:reduce){.dk *{transition:none;animation:none}}
JavaScript
// Count-up on view. The text inside each [data-count] element is the real, final number, so it shows as-is
// without JavaScript and under prefers-reduced-motion (no count from 0). While the digits run, an invisible copy
// of the final number keeps the width steady (digits grow from the right, next to any unit) and a visually
// hidden copy is what screen readers get.
// Optional: data-from="…" (start value, default 0) and data-duration="…" (ms, default 1800).
(() => {
const els = document.querySelectorAll('[data-count]');
if (!els.length || !('IntersectionObserver' in window) || matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const lang = document.documentElement.lang || undefined;
const sep = type => new Intl.NumberFormat(lang).formatToParts(12345.6).find(p => p.type === type)?.value;
const group = sep('group') || ',', point = sep('decimal') || '.';
const esc = s => s.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
const numRe = new RegExp(`^(\\D*?)(\\d[\\d${esc(group)}]*(?:${esc(point)}\\d+)?)(.*)$`, 's');
const ease = t => 1 - (1 - t) ** 4;
const span = (css, text) => { const s = document.createElement('span'); s.style.cssText = css; s.textContent = text; return s; };
const jobs = new WeakMap();
const run = el => {
const j = jobs.get(el), t0 = performance.now();
const tick = now => {
const p = Math.min((now - t0) / j.dur, 1);
if (p < 1) { j.live.textContent = j.pre + j.fmt.format(j.from + (j.to - j.from) * ease(p)) + j.post; requestAnimationFrame(tick); }
else { el.textContent = j.final; el.style.display = ''; }
};
requestAnimationFrame(tick);
};
const io = new IntersectionObserver(list => list.forEach(e => {
if (e.isIntersecting) { io.unobserve(e.target); run(e.target); }
}), { threshold: 0.6 });
els.forEach(el => {
const final = el.textContent, m = final.trim().match(numRe);
if (!m) return;
const [, pre, num, post] = m, dec = num.includes(point) ? num.split(point)[1].length : 0;
const to = parseFloat(num.split(group).join('').replace(point, '.')), from = parseFloat(el.dataset.from) || 0;
const fmt = new Intl.NumberFormat(lang, { minimumFractionDigits: dec, maximumFractionDigits: dec, useGrouping: num.includes(group) });
const live = span('grid-area:1/1;justify-self:end', pre + fmt.format(from) + post);
live.setAttribute('aria-hidden', 'true');
el.style.display = 'inline-grid';
el.replaceChildren(span('grid-area:1/1;visibility:hidden', final.trim()), live,
span('position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap', final.trim()));
jobs.set(el, { final, pre, post, to, from, fmt, live, dur: +el.dataset.duration || 1800 });
io.observe(el);
});
})();
/* Dark stats: theme colours for the blue-black ground and the cyan-to-violet accent. */
@theme {
--color-dk-bg: #0f121b; --color-dk-card: #131725; --color-dk-line: #262c3f; --color-dk-ink: #eef1fa; --color-dk-muted: #a3abc2;
--color-dk-cyan: #67e8f9; --color-dk-violet: #c4b5fd;
}
JavaScript
// Count-up on view. The text inside each [data-count] element is the real, final number, so it shows as-is
// without JavaScript and under prefers-reduced-motion (no count from 0). While the digits run, an invisible copy
// of the final number keeps the width steady (digits grow from the right, next to any unit) and a visually
// hidden copy is what screen readers get.
// Optional: data-from="…" (start value, default 0) and data-duration="…" (ms, default 1800).
(() => {
const els = document.querySelectorAll('[data-count]');
if (!els.length || !('IntersectionObserver' in window) || matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const lang = document.documentElement.lang || undefined;
const sep = type => new Intl.NumberFormat(lang).formatToParts(12345.6).find(p => p.type === type)?.value;
const group = sep('group') || ',', point = sep('decimal') || '.';
const esc = s => s.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
const numRe = new RegExp(`^(\\D*?)(\\d[\\d${esc(group)}]*(?:${esc(point)}\\d+)?)(.*)$`, 's');
const ease = t => 1 - (1 - t) ** 4;
const span = (css, text) => { const s = document.createElement('span'); s.style.cssText = css; s.textContent = text; return s; };
const jobs = new WeakMap();
const run = el => {
const j = jobs.get(el), t0 = performance.now();
const tick = now => {
const p = Math.min((now - t0) / j.dur, 1);
if (p < 1) { j.live.textContent = j.pre + j.fmt.format(j.from + (j.to - j.from) * ease(p)) + j.post; requestAnimationFrame(tick); }
else { el.textContent = j.final; el.style.display = ''; }
};
requestAnimationFrame(tick);
};
const io = new IntersectionObserver(list => list.forEach(e => {
if (e.isIntersecting) { io.unobserve(e.target); run(e.target); }
}), { threshold: 0.6 });
els.forEach(el => {
const final = el.textContent, m = final.trim().match(numRe);
if (!m) return;
const [, pre, num, post] = m, dec = num.includes(point) ? num.split(point)[1].length : 0;
const to = parseFloat(num.split(group).join('').replace(point, '.')), from = parseFloat(el.dataset.from) || 0;
const fmt = new Intl.NumberFormat(lang, { minimumFractionDigits: dec, maximumFractionDigits: dec, useGrouping: num.includes(group) });
const live = span('grid-area:1/1;justify-self:end', pre + fmt.format(from) + post);
live.setAttribute('aria-hidden', 'true');
el.style.display = 'inline-grid';
el.replaceChildren(span('grid-area:1/1;visibility:hidden', final.trim()), live,
span('position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap', final.trim()));
jobs.set(el, { final, pre, post, to, from, fmt, live, dur: +el.dataset.duration || 1800 });
io.observe(el);
});
})();
About this set
Stats sections for dark landing pages, where light-theme numbers lose their contrast. Glow band with gradient numbers sets four figures in cyan-to-violet gradient text over two soft radial glows, for a developer or infrastructure product. Bordered grid with monospace labels places six stats in a grid with 1px gaps, each with a monospace key such as cold start, a large number and a short note. Spotlight figure gives one large figure a violet glow and puts three supporting stats on a ruled row underneath. The numbers count up on view with the same short script as the count-up set, and the final values are plain text in the HTML. Gradient text uses background-clip: text inside an @supports block, so browsers without it fall back to a solid light colour. The Bootstrap version runs in data-bs-theme=”dark” with the body and secondary colours retuned, and the Tailwind version uses bg-clip-text and gradient utilities.
What’s included
Gradient numbers with a solid-colour fallback
Bordered grid built from 1px gaps, so it wraps cleanly
Spotlight figure with a radial glow
Count-up on view, final values in the HTML
Bootstrap version uses data-bs-theme="dark"
Accessibility
Labels and notes use a light grey checked for WCAG AA contrast on the blue-black cards, and the gradient runs between two light colours so every part of a number stays readable. While numbers count, screen readers get a hidden copy of the final value, and with prefers-reduced-motion they show at their final values straight away. Each section has a heading or label.
Customise it
Change --dk-cyan and --dk-violet for the gradient and glow, and --dk-bg, --dk-card and --dk-line for the surfaces, all on .dk. In Bootstrap the same variables sit on .dk with --bs-body-color and --bs-secondary-color; in Tailwind edit the --color-dk-* theme colours.