Installation
pnpm dlx shadcn@latest add https://asciicn.dev/r/glyph-trail.jsonUsage
import { GlyphTrail } from "@/components/asciicn/glyph-trail";
export function Hero() {
return (
<GlyphTrail className="flex min-h-[480px] items-center justify-center">
<h1>Your headline</h1>
</GlyphTrail>
);
}Move the mouse, a pen or a finger across the box. The pointer lays puffs of glyphs along its path. Each one starts hot, in the densest glyphs of the charset and the hot colour, then cools: its glyphs thin out toward the sparse end of the charset, its colour fades to cool, and it drifts up and aside on a slow current before it vanishes. The tail frays into wisps while the head stays solid.
Speed sets how much is laid down. A slow move leaves a short, thin line. A quick flick lays hotter, wider puffs, throws a few single-glyph sparks and leaves a long comet behind it.
While nobody draws, a wanderer traces a slow loop across the box and leaves the same trail, so the first frame is never empty. It fades out when the pointer starts drawing and comes back after a few seconds of stillness. Set idle={false} for a layer that stays blank until someone touches it.
Children render above the trail. The trail clears itself behind their text and controls, so a headline keeps its contrast while the smoke flows around it. Set clearContent={false} to let it pass straight behind. With no children it is a plain layer: place it with className, behind or over a section.
<section className="relative">
<YourContent />
<GlyphTrail className="absolute inset-0 z-10" />
</section>Examples
A hero with a button
The headline and the button are children, so they sit above the trail, which clears around them. The trail never takes pointer events, so the button clicks as usual.
A small card
Braille draws the trail in fine dots on a card. A wider spread keeps the dots in a full plume at a small cell size.
Props
Every other div prop goes to the wrapper. charset, chars, phosphor, grade, threshold, edgeThreshold, fx, speed, animate and handle work as on the ASCII image. dither defaults to "bayer4" for Braille and quadrants, so the smoke keeps a dot gradient.
| Prop | Type | Default |
|---|---|---|
| decay How long a glyph takes to cool and vanish, in seconds. | Type: number | Default 2.4 |
| spread How wide the trail fans out around the path, in cells. | Type: number | Default 1.8 |
| idle A slow wanderer draws the trail while nobody moves the pointer. | Type: boolean | Default true |
| charset Which glyphs draw the trail, densest at the head. | Type: CharsetName | Default "standard" |
| colorMode "truecolor" cools from hot to cool, "duotone" uses ramp, "mono" draws in the ink. "mono" when a phosphor is set and no mode is. | Type: "truecolor" | "duotone" | "mono" | Default "truecolor" |
| hot The colour of a fresh glyph in truecolor. | Type: AsciiColor | Default "orange" |
| cool The colour a glyph cools to in truecolor, and the ink in mono. | Type: AsciiColor | Default "ink" |
| ramp Duotone: a ramp name or stops as #rrggbb, cold to hot. | Type: RampName | string[] | Default "ember" |
| background Fill the box behind the glyphs. "paper" when a phosphor is set. | Type: "paper" | "transparent" | Default "transparent" |
| cell Glyph cell height in CSS pixels. | Type: number | Default 14 |
| fps Frame rate cap while the pointer draws. The wanderer runs at up to 30. | Type: number | Default 60 |
| stillTime Where the wanderer's path ends in the still frame drawn under reduced motion, in seconds. | Type: number | Default 7 |
| clearContent Clear the trail behind the children's text and controls so they stay readable. | Type: boolean | Default true |
| children Content drawn above the trail. | Type: ReactNode | Default – |
Notes
- The canvas is decorative and hidden from assistive technology. Children stay in the accessibility tree as usual.
- The canvas has
pointer-events: none. Moves are read fromwindowand kept only when they land inside the box, so links and buttons under or over the trail still work. With no children the wrapper ignores pointer events too. - Touch is read without blocking scroll: a finger dragging down the page scrolls it and leaves a trail.
- Under reduced motion it draws one still frame of the wanderer's path at
stillTimeand ignores the pointer. Withidle={false}that frame is empty. - It pauses offscreen and in hidden tabs. With
idle={false}the loop stops entirely once the last glyph has burned out, and starts again when the pointer enters the box. - The tail is drawn translucent, so it fades into whatever is behind the box. On a light page the hottest glyphs deepen instead of paling.