Ascii Art
Turn an image into animatable ASCII or pixel art.
Downsamples any image (or text/emoji) in real time into ASCII characters or pixel blocks, revealing it with a scramble, wave, or scan animation. Everything is drawn with canvas 2D, and it respects "reduce motion".
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add ascii-art.enASCII Art is a React image-to-ASCII component (an ASCII art generator) drawn with canvas 2D. It downsamples an image or a piece of text into a character grid, maps brightness to denser or lighter glyphs, can switch to colored pixel blocks, and ships with scramble, wave and diagonal reveal animations. Motion is used only to read the reduced-motion setting.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<AsciiArt/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- Hero visuals for developer tools, CLI products or terminal-style sites
- A 404 or maintenance page with a large animated "404"
- Logos or speaker portraits on hackathon and tech event pages
- Experimental, retro-computing sections in a portfolio
Installation
npx webberui@latest add https://webberui.com/r/ascii-art.en.jsonOr, once registries are configured in components.json, install it as @webberui/ascii-art.
Usage
Pass an image through src (a data: URI is recommended to keep the component self-contained), or use text to treat a string or emoji as the source graphic.
import { AsciiArt } from "@/components/ui/ascii-art";
<AsciiArt src="/portrait.png" columns={90} animation="scramble" loop />
<AsciiArt text="★" mode="pixel" animation="reveal" />Tip:
columnsis the number of horizontal character columns — larger is finer but heavier. A cross-origin image without CORS headers taints the canvas and cannot be sampled, so a same-origin resource or adata:URI is the safest choice.
Examples
404 page
Without src, the text is first drawn as large bold type and then converted to characters; the wave animation keeps running.
import { AsciiArt } from "@/components/ui/ascii-art";
export default function NotFound() {
return (
<main className="grid min-h-[60vh] place-items-center gap-6 text-emerald-500">
<AsciiArt text="404" alt="404 page not found" columns={80} animation="wave" />
<p className="text-neutral-500">This page doesn't exist or has been removed.</p>
</main>
);
}When color is not set, the canvas reads its currentColor, so the parent's text-emerald-500 sets the glyph color.
Pixel-mode logo reveal
mode="pixel" keeps the image's original colors and outputs square blocks; reveal sweeps the picture in diagonally from the top-left.
<AsciiArt
src="/brand/logo.png"
alt="Company logo"
mode="pixel"
animation="reveal"
columns={48}
fontSize={6}
background="#0a0a0a"
/>Custom ramp on a light background
chars runs from dark to bright. On a white page, the dark subject should get the denser glyphs, so add invert to flip the brightness mapping.
<AsciiArt
src="/team/avatar.jpg"
alt="Speaker portrait"
chars=" ░▒▓█"
invert
columns={64}
animation="scramble"
speed={0.8}
className="text-neutral-900"
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Source image URL or data: URI |
text | string | "★" | Without src, this text or emoji is used as the source graphic instead |
alt | string | — | Alternative text for assistive technology |
columns | number | 72 | Horizontal resolution (number of character columns) |
chars | string | " .:-=+*#%@" | Character ramp from dark to light, at least two characters |
color | string | currentColor | Text color in ascii mode |
background | string | "transparent" | Background color |
mode | "ascii" | "pixel" | "ascii" | Output characters or colored pixel blocks |
animation | "scramble" | "wave" | "reveal" | "none" | "scramble" | Animation mode |
speed | number | 1 | Animation speed multiplier |
fontSize | number | 9 | Pixel height of a single cell (the font size in ascii mode) |
invert | boolean | false | Invert the brightness mapping |
loop | boolean | false | Whether to keep looping after the animation finishes |
Accessibility
- When the user has "reduce motion" enabled at the system level, the final static frame is drawn directly with no animation
- The canvas carries
role="img"and anaria-label, so the alternative text is announced rather than the individual cells
FAQ
How is the output size calculated?
Canvas width comes from columns and fontSize: each cell is about fontSize × 0.6 px wide in ascii mode and a fontSize square in pixel mode; the row count follows the source image's aspect ratio. The canvas has max-w-full, so it never overflows its container.
Why doesn't my image show up?
The component has to read pixel data. A cross-origin image served without CORS headers taints the canvas, sampling fails and the component silently skips it. Same-origin paths or data: URIs are the safest option.
Does it keep using CPU while on the page?
scramble and reveal stop drawing once they finish (unless loop is set). wave and loop run continuously, but an IntersectionObserver pauses requestAnimationFrame when the canvas scrolls out of view. Larger columns means more glyphs per frame.
Does the color follow light/dark theme changes?
The default color is read from currentColor once when the canvas is drawn, so it does not update on a theme switch unless the component re-renders. Pass color when you need explicit colors.
Does it work with the Next.js App Router?
Yes. The source already has "use client", and image loading and drawing happen inside an effect, so the server only outputs an empty canvas. Dependencies are motion, clsx and tailwind-merge, or copy the source from the Manual tab.