Grid Beams

A ruled grid with neon beams running down random lines, fading out toward the edges.

Loading preview

Installation

npx shadcn@latest add @loomui/grid-beams

Usage

import { GridBeams } from "@/components/ui/grid-beams"

The layer is absolutely positioned, so it wants a positioned parent that clips:

<section className="relative overflow-hidden">
  <GridBeams />
  <div className="relative">{children}</div>
</section>

The grid lines are drawn with currentColor. They take their ink from the surface instead of carrying a colour that only reads on one theme. The beams are the opposite: they are meant to be loud, and they keep their own colours in both themes.

<GridBeams
  cellSize={48}
  beams={12}
  colors={["#22d3ee", "#a855f7"]}
  duration={3}
/>

How the travel works

Each beam spans the whole axis it travels, with the streak painted into the leading slice of it, sized by length. Translating that element by a percentage is therefore a percentage of the container, so a beam crosses a box of any size with no per-frame work at all. Once the beams are placed the whole effect is on the compositor.

Beams sit on real grid lines, at multiples of cellSize from the edge, so they line up with the ruling exactly. Which lines exist depends on the container size. The layer measures itself once with a ResizeObserver and picks from the lines actually on screen. The first and last are skipped, because the layer clips and a beam on either would be sliced in half against the edge.

Props

PropTypeDefaultDescription
cellSizenumber64Grid cell size in pixels.
beamsnumber7How many beams travel at once.
colorsstring[]four neonsColours a beam is picked from.
durationnumber4Seconds for one beam to cross the container.
lengthstring"24%"Beam length, as a share of container height.
thicknessnumber2Beam thickness in pixels.
lineOpacitynumber0.05Opacity of the static grid lines.
axis"vertical" | "horizontal" | "both""both"Which way beams travel.
fadeboolean | stringtrueFade the layer. A string is a raw CSS mask.
seednumber1Changes which lines are chosen.
disabledbooleanfalseRender the grid with no beams.
classNamestringnoneMerged onto the layer.

Any other <div> prop is forwarded.

Restraint

Seven beams on a hero is a texture. Twenty is a screensaver. If you want more going on, raise beams a little and lower duration instead, so the eye reads speed rather than clutter.

Accessibility

The layer is aria-hidden and never takes pointer events. Under prefers-reduced-motion the beams are not drawn at all and the ruled grid is left in place, which is the part that was never moving.