A light hero with the layout grid showing through. Five hairline guides run the full height of the section, the outer two solid and the inner ones dashed, and short coloured glows slide along a few of them, some falling and some rising. Every few seconds a different set of guides lights up. A slanted pastel band sits behind the top of the section, and the hero content (headline, two buttons and a small payments card) sits over the lines. On phones two of the guides are hidden so the lines never crowd the text.
// Adapted from Cult UI (https://www.cult-ui.com/docs/components/stripe-bg-guides)
// Copyright (c) 2023 Jordan-Gilliam. MIT License: https://we.inc/components/licenses
import { useEffect, useState } from "react";
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
import { ArrowRight, Check } from "lucide-react";
/** One entry per vertical guide. `solid` draws a full line instead of dashes. */
const guides = [
{ solid: true, glow: "#6366f1" },
{ solid: false, glow: "#d946ef" },
{ solid: false, glow: "#0ea5e9" },
{ solid: false, glow: "#6366f1" },
{ solid: true, glow: "#f59e0b" },
];
const GLOW_HEIGHT = 180; // pixels
const TRAVEL_SECONDS = 5.5;
const SHUFFLE_MS = 6000; // how often a new set of guides lights up
const MAX_ACTIVE = 3;
const rows = [
{ who: "Okoro & Field Studio", what: "Invoice 0418", amount: "$2,400.00", state: "Paid" },
{ who: "Lantern Cycle Hire", what: "Invoice 0419", amount: "$860.00", state: "Paid" },
{ who: "Marguerite Bakes", what: "Invoice 0420", amount: "$315.50", state: "Reminder sent" },
];
/** Picks which guides carry a glow: a fixed first set, then a rotation, never more than MAX_ACTIVE. */
function activeSet(tick: number): boolean[] {
return guides.map((_, i) => {
const slot = (i + tick) % guides.length;
return slot < MAX_ACTIVE;
});
}
function Guides() {
const reduce = useReducedMotion();
const [tick, setTick] = useState(0);
useEffect(() => {
if (reduce) return;
const t = window.setInterval(() => setTick((n) => n + 2), SHUFFLE_MS);
return () => window.clearInterval(t);
}, [reduce]);
const active = activeSet(tick);
return (
<div aria-hidden="true" className="pointer-events-none absolute inset-0 px-4 sm:px-6 lg:px-24">
<div className="mx-auto flex h-full max-w-6xl justify-between">
{guides.map((g, i) => (
<div
key={i}
className={`relative h-full w-px ${i === 1 || i === 3 ? "hidden sm:block" : ""}`}
style={g.solid ? { background: "rgb(226 232 240)" } : { backgroundImage: "linear-gradient(to bottom, rgb(203 213 225) 50%, transparent 50%)", backgroundSize: "1px 8px" }}
>
<AnimatePresence>
{active[i] && (
<motion.div
key={`glow-${i}-${tick}`}
className="absolute -left-px w-[3px] rounded-full"
style={{
height: GLOW_HEIGHT,
background: i % 2 === 0 ? `linear-gradient(to bottom, transparent, ${g.glow})` : `linear-gradient(to top, transparent, ${g.glow})`,
filter: `drop-shadow(0 0 6px ${g.glow})`,
}}
initial={reduce ? { top: "35%", opacity: 0.7 } : { top: i % 2 === 0 ? "-30%" : "110%", opacity: 0 }}
animate={reduce ? { top: "35%", opacity: 0.7 } : { top: i % 2 === 0 ? ["-30%", "110%"] : ["110%", "-30%"], opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
top: { duration: TRAVEL_SECONDS + i * 0.6, repeat: Infinity, ease: [0.4, 0, 0.2, 1], delay: tick === 0 ? i * 0.25 : i * 0.4 },
opacity: { duration: 0.6 },
}}
/>
)}
</AnimatePresence>
</div>
))}
</div>
</div>
);
}
export default function DashedColumnGuidesBackgroundWithTravellingGlows() {
return (
<section className="relative isolate overflow-hidden bg-white">
<div aria-hidden="true" className="absolute inset-x-0 top-0 -z-10 h-[420px] origin-top-left -skew-y-6 bg-gradient-to-r from-indigo-100 via-fuchsia-50 to-amber-50" />
<Guides />
<div className="relative mx-auto grid max-w-6xl items-center gap-12 px-4 py-16 sm:px-6 sm:py-28 lg:grid-cols-2 lg:px-24">
<div>
<p className="inline-flex items-center gap-2 rounded-full bg-white px-3 py-1 text-xs font-semibold text-indigo-700 shadow-sm ring-1 ring-indigo-100">
<span className="h-1.5 w-1.5 rounded-full bg-indigo-500" /> New: automatic late reminders
</p>
<h1 className="mt-5 text-4xl font-semibold tracking-tight text-slate-900 sm:text-5xl">Invoices that chase themselves.</h1>
<p className="mt-4 max-w-md text-lg text-slate-600">Tallybook sends the invoice, takes the card payment and follows up politely, so a small studio gets paid without anyone writing an awkward email.</p>
<div className="mt-8 flex flex-wrap items-center gap-3">
<a href="#" className="inline-flex h-12 items-center gap-2 rounded-full bg-slate-900 px-6 text-sm font-semibold text-white outline-none transition hover:bg-slate-700 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2">
Start free <ArrowRight className="h-4 w-4" aria-hidden="true" />
</a>
<a href="#" className="inline-flex h-12 items-center rounded-full px-5 text-sm font-semibold text-slate-900 outline-none hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-indigo-500">
See a sample invoice
</a>
</div>
</div>
<div className="rounded-2xl bg-white p-5 shadow-2xl shadow-indigo-200/60 ring-1 ring-slate-200 sm:p-6">
<div className="flex items-baseline justify-between">
<h2 className="text-sm font-semibold text-slate-900">Paid this week</h2>
<p className="text-xs font-medium text-slate-600">May 12 to 18</p>
</div>
<p className="mt-2 text-4xl font-semibold tracking-tight tabular-nums text-slate-900">$3,575.50</p>
<ul className="mt-5 divide-y divide-slate-100">
{rows.map((r) => (
<li key={r.what} className="flex items-center gap-3 py-3">
<span className={`grid h-8 w-8 shrink-0 place-items-center rounded-full ${r.state === "Paid" ? "bg-emerald-100 text-emerald-700" : "bg-amber-100 text-amber-800"}`}>
{r.state === "Paid" ? <Check className="h-4 w-4" aria-hidden="true" /> : <span className="text-xs font-bold">!</span>}
</span>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-slate-900">{r.who}</p>
<p className="text-xs text-slate-600">
{r.what} ยท {r.state}
</p>
</div>
<p className="text-sm font-semibold tabular-nums text-slate-900">{r.amount}</p>
</li>
))}
</ul>
</div>
</div>
</section>
);
}
Install with one command: npx shadcn@latest add https://we.inc/r/dashed-column-guides-background-with-travelling-glows.json
Each guide is a one pixel wide `div`. Its background is a vertical gradient that is solid for half of an eight pixel tile and transparent for the other half, repeated down the line. No SVG or border tricks are needed.
Each glow is a three pixel wide gradient block positioned inside its guide and centred on it, with a soft `drop-shadow` in the same colour. The section clips anything that travels past its top or bottom edge.
Yes. Change the section to a dark background, set the line colours to something like `rgb(30 41 59)`, and pick brighter `glow` colours. Remove or recolour the pastel band.
The guide layer uses the same horizontal padding and `max-w-6xl` as the content wrapper, with `justify-between` spacing. Keep those classes in sync and the outer guides will sit on the content edges.
No. At most three small elements animate at a time, each moving one property on a box three pixels wide.
Adapted from Stripe Bg Guides in Cult UI, used under its open-source licence. Licences and credits
Related: Animated Grid Background with Travelling Light Beams, Grid Background with Fading Random Squares, Hero with Live Updating Dashboard Mock.
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.