Type
Display type built from glyphs. Any font, no FIGlet files.
T-minus 3 days
Controls
pnpm dlx shadcn@latest add @asciicn/ascii-textPulls in the engine at lib/ascii on first install. The @asciicn namespace needs one line in components.json, see Installation.
Install the engine first with npx shadcn@latest add @asciicn/ascii-core, or copy lib/ascii from the repository.
Copy this into components/ascii/ascii-text.tsx:
"use client"
import { useEffect, useState } from "react"
import { AsciiCanvas, type AsciiCanvasProps } from "@/lib/ascii"
import { cn } from "@/lib/utils"
export type AsciiTextProps = Omit<AsciiCanvasProps, "source" | "field" | "frame" | "ink"> & {
text: string
/** CSS font used to rasterise the letters before they are filled with glyphs. */
font?: string
}
/** Big display type drawn out of smaller characters. Any font, no FIGlet files. */
export function AsciiText({
text,
font = "900 160px ui-sans-serif, system-ui, sans-serif",
fontSize = 8,
charset = "blocks",
className,
...props
}: AsciiTextProps) {
const [mask, setMask] = useState<HTMLCanvasElement | null>(null)
useEffect(() => {
let live = true
document.fonts.ready.then(() => {
if (!live) return
const canvas = document.createElement("canvas")
const ctx = canvas.getContext("2d")!
ctx.font = font
const m = ctx.measureText(text)
const pad = m.actualBoundingBoxAscent * 0.1
canvas.width = Math.ceil(m.width + pad * 2)
canvas.height = Math.ceil(m.actualBoundingBoxAscent + m.actualBoundingBoxDescent + pad * 2)
ctx.font = font
ctx.fillStyle = "#fff"
ctx.textBaseline = "alphabetic"
ctx.fillText(text, pad, pad + m.actualBoundingBoxAscent)
setMask(canvas)
})
return () => {
live = false
}
}, [text, font])
return (
<div
className={cn("relative w-full", className)}
style={{ aspectRatio: mask ? `${mask.width} / ${mask.height}` : "5 / 1" }}
>
<AsciiCanvas
source={mask}
ink="light"
fit="contain"
fontSize={fontSize}
charset={charset}
aria-label={text}
className="absolute inset-0"
{...props}
/>
</div>
)
}
import { AsciiText } from "@/components/ascii/ascii-text"
<AsciiText text="launch" className="text-primary" />font takes any CSS font shorthand. Braille dots trace the curves of a serif far more finely than full glyphs.
Big type carries an empty page on its own. Keep a real <h1> for screen readers next to it.
This page drifted off the grid.
| Prop | Type | Default | Description |
|---|---|---|---|
| text | string | — | What to write. Also the accessible label. |
| font | string | "900 160px ui-sans-serif…" | Any CSS font, rasterised then filled with glyphs. |
| fontSize | number | 8 | Glyph size, so smaller means finer letters. |
| charset | string | "blocks" | ░▒▓█ gives soft anti-aliased edges. |
| Prop | Type | Default | Description |
|---|---|---|---|
| charset | CharsetName | string | "standard" | Named ramp or your own string, empty to dense. "braille" packs 2×4 dots per cell, "quadrants" 2×2 blocks. |
| fontSize | number | 12 | Cell size in CSS pixels. Smaller means more detail. |
| dither | "none" | "bayer" | "floyd" | "none" | Quantisation. Floyd–Steinberg for photos, Bayer for a retro look. |
| color | "ink" | "source" | string[] | "ink" | Ink uses the CSS colour (theme with text-*). Source keeps pixel colours. An array maps tone onto gradient stops, e.g. palettes.thermal. |
| edges | number | 0 | Outline contours with | / - \ where the tone gradient beats this strength. Try 0.3. Ramps only. |
| opacity | boolean | false | Fade each glyph by its tone. |
| brightness | number | 0 | Added after contrast, -1 to 1. |
| contrast | number | 1 | Multiplier around mid grey. |
| gamma | number | 1 | Tone curve exponent. |
| invert | boolean | false | Swap ink and blank. |
| animate | boolean | auto | On for fields, frames and video. Off under reduced motion, except video. |
| fps | number | 30 | Frame cap. |
| ref | Ref<AsciiHandle> | — | Exposes text(), svg(), png() and the canvas. |
text, so screen readers read the word, not the glyphs. On headings, keep a visually hidden <h1> too.prefers-reduced-motion it freezes on the current frame.color, so text-primary or text-foreground/40 themes them. Theme changes repaint at once.ref.current.text(), svg() or png().