A dark hero with a field of tiny amber squares behind it. Each square changes brightness at random moments, so the whole surface shimmers like a panel of indicator lights. The grid is drawn on a canvas, masked so it is brightest behind the right-hand side and faded out behind the text. The hero has a headline, two buttons and a terminal-style card with a deploy log. On phones the card moves under the copy and the grid fills the whole section.
// Adapted from Magic UI (https://magicui.design/docs/components/flickering-grid)
// Copyright (c) Magic UI. MIT License: https://we.inc/components/licenses
import { useEffect, useRef } from "react";
import { ArrowRight, Terminal } from "lucide-react";
const lines = [
{ time: "09:14:02", text: "build started on 4 runners", tone: "text-stone-400" },
{ time: "09:14:31", text: "1,208 tests passed, 0 flaky", tone: "text-stone-200" },
{ time: "09:14:44", text: "image pushed to registry", tone: "text-stone-200" },
{ time: "09:14:52", text: "live in 3 regions", tone: "text-amber-300" },
];
/**
* A canvas of small squares that each change brightness at random.
* `color` is "r, g, b". The loop stops while the canvas is off screen.
*/
function FlickeringGrid({ squareSize = 4, gridGap = 6, flickerChance = 0.3, color = "251, 191, 36", maxOpacity = 0.9, className = "" }: { squareSize?: number; gridGap?: number; flickerChance?: number; color?: string; maxOpacity?: number; className?: string }) {
const boxRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const box = boxRef.current;
const canvas = canvasRef.current;
const ctx = canvas?.getContext("2d");
if (!box || !canvas || !ctx) return;
const still = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const step = squareSize + gridGap;
let cols = 0;
let rows = 0;
let dpr = 1;
let squares = new Float32Array(0);
let frame = 0;
let last = 0;
let visible = false;
const draw = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
ctx.fillStyle = `rgba(${color}, ${squares[i * rows + j]})`;
ctx.fillRect(i * step * dpr, j * step * dpr, squareSize * dpr, squareSize * dpr);
}
}
};
const setup = () => {
const w = box.clientWidth;
const h = box.clientHeight;
dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = w * dpr;
canvas.height = h * dpr;
cols = Math.ceil(w / step);
rows = Math.ceil(h / step);
squares = new Float32Array(cols * rows);
for (let i = 0; i < squares.length; i++) squares[i] = Math.random() * maxOpacity;
draw();
};
const loop = (time: number) => {
const dt = Math.min((time - last) / 1000, 0.1);
last = time;
for (let i = 0; i < squares.length; i++) {
if (Math.random() < flickerChance * dt) squares[i] = Math.random() * maxOpacity;
}
draw();
frame = requestAnimationFrame(loop);
};
const start = () => {
cancelAnimationFrame(frame);
if (!visible || still || document.hidden) return;
last = performance.now();
frame = requestAnimationFrame(loop);
};
setup();
const ro = new ResizeObserver(setup);
ro.observe(box);
const io = new IntersectionObserver(([entry]) => {
visible = entry.isIntersecting;
start();
});
io.observe(canvas);
document.addEventListener("visibilitychange", start);
return () => {
cancelAnimationFrame(frame);
ro.disconnect();
io.disconnect();
document.removeEventListener("visibilitychange", start);
};
}, [squareSize, gridGap, flickerChance, color, maxOpacity]);
return (
<div ref={boxRef} aria-hidden="true" className={`pointer-events-none absolute inset-0 ${className}`}>
<canvas ref={canvasRef} className="h-full w-full" />
</div>
);
}
export default function FlickeringPixelGridCanvasBackground() {
return (
<section className="relative overflow-hidden bg-stone-950 px-5 py-20 text-stone-100 sm:px-6 sm:py-28">
<FlickeringGrid className="[mask-image:radial-gradient(ellipse_90%_85%_at_70%_45%,black,transparent)]" />
<span aria-hidden="true" className="pointer-events-none absolute inset-0 bg-gradient-to-r from-stone-950 via-stone-950/50 to-transparent" />
<div className="relative mx-auto grid max-w-6xl items-center gap-12 lg:grid-cols-2">
<div>
<p className="inline-flex items-center gap-2 font-mono text-sm text-amber-300">
<Terminal aria-hidden="true" className="h-4 w-4" />
foundry deploy
</p>
<h1 className="mt-5 text-4xl font-semibold tracking-tight text-white sm:text-6xl sm:leading-[1.05]">Merged at nine. Live by quarter past.</h1>
<p className="mt-6 max-w-lg text-lg leading-8 text-stone-300">Foundry runs your tests on as many machines as it takes, then rolls the build out region by region and stops if anything looks wrong.</p>
<div className="mt-9 flex flex-wrap gap-3">
<a href="#" className="group inline-flex h-12 items-center gap-2 rounded-lg bg-amber-400 px-6 text-sm font-semibold text-stone-950 transition hover:bg-amber-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-300 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-950">
Connect a repository
<ArrowRight aria-hidden="true" className="h-4 w-4 transition-transform group-hover:translate-x-1" />
</a>
<a href="#" className="inline-flex h-12 items-center rounded-lg border border-stone-700 px-6 text-sm font-semibold text-white transition hover:border-stone-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-300">
Read the docs
</a>
</div>
</div>
<div className="rounded-2xl border border-stone-800 bg-stone-950/80 shadow-2xl shadow-black/60">
<div className="flex items-center gap-2 border-b border-stone-800 px-4 py-3">
<span className="h-2.5 w-2.5 rounded-full bg-stone-700" />
<span className="h-2.5 w-2.5 rounded-full bg-stone-700" />
<span className="h-2.5 w-2.5 rounded-full bg-stone-700" />
<span className="ml-2 font-mono text-xs text-stone-400">release 218, main</span>
</div>
<ul className="space-y-3 p-5 font-mono text-sm">
{lines.map((l) => (
<li key={l.time} className="flex gap-4">
<span className="shrink-0 text-stone-500">{l.time}</span>
<span className={l.tone}>{l.text}</span>
</li>
))}
</ul>
<div className="flex items-center justify-between border-t border-stone-800 px-5 py-4 text-sm">
<span className="text-stone-400">Total time</span>
<span className="font-mono font-semibold text-white">50 seconds</span>
</div>
</div>
</div>
</section>
);
}
Install with one command: npx shadcn@latest add https://we.inc/r/flickering-pixel-grid-canvas-background.json
A section this size holds several thousand squares. As elements they would be slow to create and to restyle. A canvas redraws them all as plain rectangles in well under a millisecond or two per frame.
Every square has an opacity stored in a typed array. Each frame, every square has a small chance, scaled by the time since the last frame, of being given a new random opacity. Then the whole grid is redrawn.
No. An `IntersectionObserver` stops the loop while the canvas is off screen, and it also stops while the browser tab is hidden. It starts again when the section comes back.
Yes. The canvas is sized by the device pixel ratio, capped at 2 to keep the work bounded, and the squares are drawn at that scale.
A `ResizeObserver` on the wrapper rebuilds the grid whenever the size changes, so it always covers its parent. The parent needs `relative` and `overflow-hidden`.
Adapted from Flickering Grid in Magic UI, used under its open-source licence. Licences and credits
Related: Dot Matrix Background with Ripples and Pointer Spotlight, Particle Network Background with Connecting Lines, Grid Background with Fading Random Squares.
More animated backgrounds 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.