Installation
pnpm dlx shadcn@latest add https://asciicn.dev/r/ascii-image.jsonUsage
import { AsciiImage } from "@/components/asciicn/ascii-image";
export function Cover() {
return (
<AsciiImage
src="/photos/launch.jpg"
alt="The launch pad at dawn"
charset="dense"
reveal="spotlight"
className="aspect-video w-full"
/>
);
}The image is sampled on the CPU. Each glyph takes the area average of the pixels under its cell, so fine detail averages out instead of flickering into noise. Braille samples 2×4 dots per cell and quadrants 2×2, which gives those charsets four to eight times the detail of a ramp at the same cell size.
Switching charset or colorMode redraws from the samples already taken. Resizing, a new fit or a new src samples the image again, once.
On a dark page, bright pixels draw dense glyphs. On a light page it flips, so dark pixels draw dense glyphs and the photo still reads the right way round. Set invert to pick one yourself. Phosphor and duotone looks bring their own dark glass and never flip.
autoLevels stretches the image so its darkest and brightest 1% of pixels reach the ends of the ramp. Flat, foggy photos gain the most. Turn it off to keep a photo's own exposure, then use grade to adjust it.
Examples
Amber phosphor in Braille
Two by four dots per cell in one ink, with the P3 amber glass behind it.
See the photo under the pointer
reveal="photo" opens a soft window onto the original file around the pointer.
Edges and blocks
charset="edge" draws strong contours as - / | \ strokes across the gradient and fills the rest from a ramp. Next to it, ANSI blocks through the sodium ramp.
Props
| Prop | Type | Default |
|---|---|---|
| src Image URL. Files on another origin need CORS headers, or the canvas can't read their pixels. | Type: string | Default – |
| alt What the image shows. Read out by screen readers. Pass "" for a decorative image. | Type: string | Default – |
| charset Which glyphs draw the tones. | Type: "standard" | "dense" | "blocks" | "quadrants" | "braille" | "cp437" | "petscii" | "katakana" | "dots" | "edge" | Default "standard" |
| chars A custom ramp from empty to dense, such as " .oO@". Wins over charset. | Type: string | Default – |
| colorMode truecolor takes each cell's own colour. ansi16 snaps it to 16 terminal colours. mono draws in one ink. duotone and palette colour glyphs by tone. | Type: "mono" | "truecolor" | "ansi16" | "duotone" | "palette" | Default "truecolor" |
| phosphor With mono: a CRT ink and its dark glass. | Type: "p1" | "p3" | "ibm" | "blueprint" | Default – |
| ramp With duotone: a named ramp or your own stops, dark to light. | Type: RampName | string[] | Default "mainframe" |
| ansi With ansi16: which 16-colour table. | Type: "vga" | "xterm" | Default "vga" |
| grade Tone and colour grading before glyphs are chosen. | Type: { brightness?, contrast?, gamma?, saturation? } | Default – |
| invert true: dark pixels draw dense glyphs. false: bright pixels do. Unset, it follows the page. | Type: boolean | Default follows the theme |
| threshold Braille and quadrants: the tone that lights a dot. | Type: number | Default 0.5 |
| edgeThreshold Edge charset: how strong a contour must be to draw a stroke. | Type: number | Default 0.22 |
| dither Ordered dithering between glyph levels, which smooths gradients in short ramps. | Type: "none" | "bayer2" | "bayer4" | "bayer8" | "blue-noise" | Default "none" |
| background transparent draws glyphs only, over whatever is behind. | Type: "paper" | "transparent" | Default "paper" |
| fit How the image fills its box, as in object-fit. | Type: "cover" | "contain" | "fill" | Default "cover" |
| focus Where the image sits when it overflows or underfills, 0 to 1 per axis. | Type: [x, y] | Default [0.5, 0.5] |
| autoLevels Stretch the image's tones to use the whole ramp. | Type: boolean | Default true |
| reveal spotlight thins glyphs away from the pointer. photo shows the original file around it. | Type: "none" | "spotlight" | "photo" | Default "none" |
| revealRadius Radius of the reveal in CSS pixels. | Type: number | Default 120 |
| intro Glyphs resolve in from noise once the image has loaded. | Type: boolean | Default true |
| cell Glyph cell height in CSS pixels. | Type: number | Default 10 |
| animate false skips the intro and the loading sweep. | Type: boolean | Default true |
| crossOrigin Passed to the image request. | Type: "anonymous" | "use-credentials" | Default "anonymous" |
| handle Filled with a handle for export: the current frame as text, ANSI, SVG or PNG. | Type: RefObject<AsciiHandle | null> | Default – |
Notes
- The wrapper is announced as an image labelled with
alt. The canvas inside is hidden from assistive technology. Withalt=""the whole thing is decorative. - While the file loads, a faint sweep of glyphs and a spinner hold its place. If it fails to load, the box says so in glyphs.
- With reduced motion, the image appears without the intro. The spotlight still follows the pointer.
- A still image draws once and then stops. Only the intro, the loading sweep and
reveal="spotlight"keep a frame loop running, and that loop pauses offscreen and in hidden tabs. - Give the box a size, such as
aspect-video w-full. The image fills it and never sets its own height.