For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. The asciicn agent skill, for building any ASCII effect on the engine, is at /.well-known/agent-skills/asciicn/SKILL.md.

ASCII engine

How the asciicn engine turns a field of tones into real glyphs, the two renderers that draw them, and the registry items you install.

Every asciicn component draws characters. A picture becomes a grid of glyph cells, each cell gets a tone between 0 (empty) and 1 (densest ink), and the engine picks the glyph that matches that tone. It installs into lib/ascii as plain source you own.

Controls
Charset
Colour
Dither levels
Invert
import { AsciiGrid, mapTones } from "@/lib/ascii";
import { AsciiShader } from "@/lib/ascii/shader";
import { toSvg } from "@/lib/ascii/export";

From tones to glyphs

Drawing a frame takes three steps, and every renderer follows them in the same order:

  1. Sample. The box is split into glyph cells, about twice as tall as they are wide. Each cell holds one sample, or a small grid of them for sub-cell charsets: 2×2 for quadrants, 2×4 for Braille. Each sample gets a tone and, optionally, a colour.
  2. Map. Tones are graded (contrast, brightness, gamma), optionally inverted, then turned into glyphs. A ramp such as " .:-=+*#%@" picks the level nearest the tone. Braille and quadrants light one dot per sample above threshold. The edge charset runs a Sobel filter and draws - / | \ along strong edges.
  3. Paint. Each glyph is drawn in its cell, coloured by the colour mode: one ink, the source colour, the nearest ANSI colour, or a ramp read by tone.

mapTones in lib/ascii/frame.ts is the reference for step 2. The GPU shader implements the same rules in GLSL, with one difference: it spaces ramp glyphs by the ink they measure in your font, where mapTones spaces them evenly. Pass mapTones the GPU's spacing with positions (see Charsets) and a frame read back from the GPU and one mapped on the CPU agree glyph for glyph.

With few glyphs, a smooth gradient breaks into bands. Set dither to a matrix such as "bayer4" and a tone between two glyphs picks the denser one in a share of the cells that matches how far along it is. Turn on Dither levels in the demo to see the bands soften.

Two renderers

You wantUseDrawn with
A full-bleed animated field, written in GLSLAsciiShaderWebGL2 and a glyph atlas
Anything computed in JavaScript: 3D, sims, mediaAsciiGridCanvas2D and the same atlas
Static art, banners, server-rendered fallbacksAsciiPreReal text in a <pre>
Glyph-filled buttons, bars and panelsfillStyle, FillPatternCSS masks and SVG patterns

AsciiShader runs your field function on the GPU for every sample, every frame. It is the fastest option by far and suits backgrounds and scenes. Write your own field walks through one.

AsciiGrid calls a draw(context) function you write in TypeScript. You fill context.frame with glyphs, by hand (frame.write, frame.put) or by building a tone field and calling mapTones. It paints at device resolution, pauses offscreen and follows the theme. Painting copies each glyph out of the shared glyph atlas and tints the whole frame in one pass, so a truecolor frame with thousands of distinct colours costs the same as a one-colour frame.

Both renderers draw blocks, shades, quadrants, Braille, dots and box lines from the same geometry in lib/ascii/glyphs.ts, snapped to whole device pixels. They never depend on which glyphs your font covers, and neighbouring blocks meet without seams. Letters, digits and katakana come from --ascii-font.

<AsciiGrid
  className="h-64"
  colored
  draw={({ frame, columns, rows, time }) => {
    for (let x = 0; x < columns; x += 1) {
      const y = Math.round(rows / 2 + Math.sin(x * 0.2 + time) * (rows / 3));
      frame.put(x, y, "█", [51, 255, 102]);
    }
  }}
/>

Text without a canvas

AsciiPre renders a frame as real text, with one <span> per colour run. It needs no JavaScript in the browser, so it works in server components, emails and RSS. Readers can select and copy it.

const frame = new MutableFrame(38, 7, { color: true });
frame.write(2, 2, eighthsBar(0.62, 22), [51, 255, 102]);
 
<AsciiPre frame={frame} cell={16} />;

It also takes a plain string: <AsciiPre frame={"+--+\n|hi|\n+--+"} />.

Glyph fills

Interface pieces fill shapes with tiled glyphs instead of flat colour. fillStyle returns inline styles that mask any element to a glyph texture. FillPattern and AsciiPattern are SVG <pattern>s for filling SVG shapes. All of them render on the server and follow the theme through CSS variables.

TextureGlyphsLooks like
shade█▓▒░·The classic ANSI gradient, densest at edge
ascii#*+=-:.The same ramp in ASCII
braille⣿ to ⠂Fine dots, a light texture for large fills
blocks█▛▚▘Chunky quadrant steps
dots●•·Halftone
hatch╱Unbroken diagonal lines
solidnoneFlat colour
<div className="h-24" style={fillStyle("braille", { color: "green", edge: "left" })} />
 
<div className="h-6" style={{ background: "var(--ascii-ink)", ...asciiMaskStyle(0.5) }} />

Ramp textures fade from from at the edge to to over length cells, or take a level for one even density. The older names gradient, dotted and hatched still work and draw shade, an even field of dots, and hatch. Glyphs in fills are drawn from geometry, because CSS masks can't load your web font. A custom ramp passed as chars with letters falls back to the system monospace font.

BloomBox, BloomGroup and bloomFilter add a phosphor glow in the fill's colour. Its strength follows --ascii-glow, so glows can be strong in dark mode and faint in light mode.

Registry items

ItemContains
ascii-coreCharsets, colour modes, frames and mapTones, cell metrics, glyph geometry and atlas, AsciiGrid, AsciiPre, CrtOverlay, media sampling, glyph fills, theme and motion hooks
ascii-shaderAsciiShader, the WebGL2 renderer
ascii-export.txt, .ans, .svg, .png and video export
ascii-tokensThe --ascii-* CSS variables
pnpm dlx shadcn@latest add https://asciicn.dev/r/ascii-core.json

Components pull in what they need as registry dependencies, so adding any component installs the engine with it. Components that only draw text or interface pieces never ship WebGL code.