Installation
pnpm dlx shadcn@latest add https://dithercn.dev/r/tunnel.jsonUsage
import { Tunnel } from "@/components/dithercn/tunnel";
export function Hero() {
return (
<section className="relative isolate min-h-[480px]">
<Tunnel className="absolute inset-0 -z-10" />
<h1>Your headline</h1>
</section>
);
}Rings come out of the fog at the vanishing point and grow as they pass you. Spokes run along the walls, and every other panel between them is lit, like tiles. The middle stays empty, so a headline fits there.
Examples
Behind a headline
spokes={0} leaves only the rings, and fewer rings spread them out. A larger fog widens the empty middle so the text sits clear of the rings.
Square corridor
shape="square" turns the rings into frames. Moving the center up a little and lowering the sway gives a steadier corridor, and a three-colour palette lights the near walls.
Props
| Prop | Type | Default |
|---|---|---|
| shape The cross-section of the tunnel. | Type: "circle" | "square" | Default "circle" |
| rings How close together the rings are. 2 packs twice as many in. | Type: number | Default 1 |
| spokes Lines along the walls, from 0 to 32. 0 leaves only the rings and drops the tiles. | Type: number | Default 12 |
| center The vanishing point, as fractions of the box from the top left. | Type: [number, number] | Default [0.5, 0.5] |
| sway How far the vanishing point wanders. 0 holds it still. | Type: number | Default 1 |
| fog Size of the empty fog in the middle. 2 doubles it to clear room for text. | Type: number | Default 1 |
| palette Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | Type: [DitherColor, DitherColor, ...DitherColor[]] | Default ["--dither-paper", "--dither-ink"] |
| matrix The threshold pattern. See Dither engine. | Type: "bayer2" | "bayer4" | "bayer8" | "blue-noise" | Default "bayer4" |
| cell Size of one dither cell in CSS pixels. | Type: number | Default --dither-cell |
| speed How fast it moves. | Type: number | Default 1 |
| animate false draws one still frame. | Type: boolean | Default true |
| className Size and position. The background fills this box. | Type: string | Default – |
Notes
- It is decorative and hidden from assistive technology. Put real text on top of it, not inside it.
- With reduced motion turned on, it draws one still frame and stops.
- It pauses when scrolled out of view or when the tab is hidden, and releases its WebGL context when far offscreen. Without WebGL it shows concentric CSS rings in the same colours.
- It costs one small draw per frame at 30 fps, at one pixel per dither cell. See Performance.