UI components
The ascii-things/ui entry is an optional React layer around the scenes. It is not a design
system: it is a composable set of primitives for the chrome a scene usually needs — framed
stages, command inputs, live readouts, scene tabs, panels, toolbars, status readouts, and
ready-made scene heroes.
It has no runtime dependencies of its own. It expects React 18+.
ROBOT DOG / TEST YARD
A small scene.
A very good dog.
The full recipe: a live engine, a framed stage, a command input, and a live readout. Type a command and the dog fetches the ball.
- CELLS
- —
- UNITS
- —
- STATE
- —
- FPS
- —
Install the stylesheet once
Section titled “Install the stylesheet once”import "ascii-things/ui.css";All components use the same CSS custom properties. Containers are square by default;
set --ascii-radius on a wrapper if your host needs rounding. Import the stylesheet from your app
shell or root layout, not from every component.
Components
Section titled “Components”AsciiStage
Section titled “AsciiStage”A framed scene surface with HUD slots.
import { AsciiDog } from "ascii-things/react";import { AsciiStage } from "ascii-things/ui";
<AsciiStage label="ROBOT DOG / TEST YARD" status="LIVE" aspectRatio="4 / 3" footer={<span>footer content</span>} overlay={<span>optional overlay</span>}> <AsciiDog palette="abyss" seed={7} /></AsciiStage>;| Prop | Type | Default | |
|---|---|---|---|
label |
ReactNode |
— | Top-left HUD label |
status |
ReactNode |
— | Top-right HUD status; adds a live dot |
overlay |
ReactNode |
— | Absolutely positioned center overlay |
footer |
ReactNode |
— | Caption below the canvas |
aspectRatio |
string | number |
"16 / 10" |
CSS aspect-ratio for the frame |
ariaLabel |
string |
— | Accessible name for the <figure> |
className |
string |
— | Host class |
style |
CSSProperties |
— | Host style |
children |
ReactNode |
— | The scene canvas |
AsciiStage gives its frame an aspect ratio, which solves the collapsed-canvas problem for
many layouts. The canvas inside still fills 100% of the frame.
AsciiPrompt
Section titled “AsciiPrompt”An accessible command input with an inline action.
import { AsciiPrompt } from "ascii-things/ui";
<AsciiPrompt label="Tell the dog what to fetch" placeholder="Throw the ball…" actionLabel="Throw" status="Awaiting command" hint="Enter submits · focus stays" onSubmit={(value) => console.log(value)}/>;| Prop | Type | Default | |
|---|---|---|---|
label |
string |
"Command" |
Screen-reader label |
placeholder |
string |
"Type a command…" |
Input placeholder |
actionLabel |
string |
"Send" |
Submit button text |
status |
ReactNode |
— | Rendered in a polite live region |
hint |
ReactNode |
— | Helper text |
value |
string |
— | Controlled value |
defaultValue |
string |
"" |
Initial uncontrolled value |
onValueChange |
(value) => void |
— | Fires on every edit |
onSubmit |
(value) => void |
— | Fires with the trimmed value |
maxLength |
number |
80 |
Input limit |
disabled |
boolean |
false |
Disable input and action |
autoFocus |
boolean |
false |
Focus the input on mount |
className |
string |
— | Host class |
style |
CSSProperties |
— | Host style |
Uncontrolled by default. Enter submits, the input clears, and focus stays in the field.
Controlled when you pass value.
AsciiReadout
Section titled “AsciiReadout”A compact label/value grid for live metrics.
import { AsciiReadout } from "ascii-things/ui";
<AsciiReadout ariaLabel="Live scene statistics" columns={4} items={[ { label: "CELLS", value: "8,640" }, { label: "UNITS", value: "01" }, { label: "STATE", value: "SNIFF" }, { label: "FPS", value: "60" }, ]}/>;| Prop | Type | Default | |
|---|---|---|---|
items |
{ label, value }[] |
— | Rows; returns null when empty |
columns |
number |
4 |
CSS grid columns |
ariaLabel |
string |
— | Accessible name for the <dl> |
className |
string |
— | Host class |
style |
CSSProperties |
— | Host style |
AsciiSceneTabs
Section titled “AsciiSceneTabs”Keyboard-navigable tabs for switching scenes, palettes, or variants.
import { useState } from "react";import type { PaletteName } from "ascii-things";import { AsciiSceneTabs } from "ascii-things/ui";
const [palette, setPalette] = useState<PaletteName>("abyss");
<AsciiSceneTabs ariaLabel="Palette" value={palette} onChange={(id) => setPalette(id as PaletteName)} scenes={[ { id: "abyss", label: "Abyss", meta: "night" }, { id: "paper", label: "Paper", meta: "day" }, { id: "neon", label: "Neon", meta: "loud" }, { id: "mono", label: "Mono", meta: "quiet" }, ]}/>;| Prop | Type | Default | |
|---|---|---|---|
scenes |
{ id, label, meta? }[] |
— | Tab definitions |
value |
string |
— | Selected id |
onChange |
(id) => void |
— | Selection callback |
ariaLabel |
string |
"Scenes" |
Tab list label |
className |
string |
— | Host class |
style |
CSSProperties |
— | Host style |
Arrow keys, Home, and End move the selection. Roving tabIndex keeps one tab in the tab
order.
AsciiDogHero
Section titled “AsciiDogHero”The copy-paste recipe: dog scene, stage, prompt, live stats, and ball throws.
import { AsciiDogHero } from "ascii-things/ui";import "ascii-things/ui.css";
export function Hero() { return <AsciiDogHero palette="abyss" seed={7} />;}AsciiDogHero extends every AsciiDogOptions prop and adds:
| Prop | Type | Default | |
|---|---|---|---|
eyebrow |
ReactNode |
"ROBOT DOG / TEST YARD" |
Kicker above the title |
title |
ReactNode |
“A small scene. / A very good dog.” | Headline |
lede |
ReactNode |
Built-in copy | Paragraph |
promptLabel |
string |
"Tell the dog what to fetch" |
Input label |
placeholder |
string |
"Tell the dog what to fetch…" |
Input placeholder |
actionLabel |
string |
"Throw" |
Submit label |
stageLabel |
string |
"ROBOT DOG / TEST YARD" |
Stage HUD label |
onCommand |
(command) => void |
— | Called after each command |
className |
string |
— | Host class |
style |
CSSProperties |
— | Host style |
It sets label to "A live ASCII robot dog test yard" unless you pass one.
Hook and primitives
Section titled “Hook and primitives”useAsciiScene
Section titled “useAsciiScene”useAsciiScene owns the canvas lifecycle, live option updates, ready handle, and stats
sampling for any scene. Use it when the built-in heroes are too opinionated.
import { createDog } from "ascii-things/dog";import { useAsciiScene } from "ascii-things/ui";
function Scene() { const { canvasRef, scene, stats } = useAsciiScene({ create: createDog, options: { palette: "auto", seed: 7 }, onReady: (handle) => handle.trigger("throwBall", 240, 180), });
return <canvas ref={canvasRef} aria-label="A live ASCII dog" />;}The hook returns canvasRef, the current scene handle, the latest stats, and a
manual refresh() function. remountKey forces a fresh world when structural options
change.
AsciiButton
Section titled “AsciiButton”<AsciiButton variant="solid" size="md">Wake</AsciiButton><AsciiButton variant="ghost">Settle</AsciiButton><AsciiButton variant="quiet" size="sm">Reset</AsciiButton>Variants: solid, ghost, quiet. Sizes: sm, md. The component forwards button
attributes and a ref.
AsciiToolbar
Section titled “AsciiToolbar”<AsciiToolbar label="Scene controls"> <AsciiButton>Wake</AsciiButton> <AsciiToggle checked={calm} onCheckedChange={setCalm} label="Calm mode" /></AsciiToolbar>A role="toolbar" flex row for related controls. Pass label for an accessible name.
AsciiToggle
Section titled “AsciiToggle”<AsciiToggle checked={calm} onCheckedChange={setCalm} label="Calm mode" hint="Settles the scene"/>A real role="switch" button with aria-checked, a label, and optional hint. The
track and thumb stay circular even though containers are square.
AsciiPanel
Section titled “AsciiPanel”<AsciiPanel eyebrow="Control surface" heading="Calm the yard." actions={<AsciiStatus label="Mode" value="LIVE" tone="success" pulse live />} footer="Square containers. Round indicators. One policy."> <AsciiToolbar label="Scene controls">...</AsciiToolbar></AsciiPanel>padding accepts none, sm, md, or lg. Header, actions, body, and footer are
all optional.
AsciiStatus
Section titled “AsciiStatus”<AsciiStatus label="Scene" value="Robot dog" tone="accent" /><AsciiStatus label="Motion" value="Running" tone="success" pulse live />Tones: neutral, accent, success, warning. pulse animates the dot; live
adds aria-live="polite".
AsciiDivider
Section titled “AsciiDivider”<AsciiDivider /><AsciiDivider label="State" /><AsciiDivider orientation="vertical" />Horizontal by default. Vertical dividers work inside toolbars and flex rows.
Scene heroes
Section titled “Scene heroes”AsciiSceneHero is the generic version of the dog hero. It selects the right engine,
action, copy, readout, and stage for any scene.
import { AsciiSceneHero } from "ascii-things/ui";
<AsciiSceneHero scene="sharks" options={{ palette: "abyss", seed: 11, density: 0.8 }} eyebrow="PRESET / SHARKS" title={<>One hero. <em>Any habitat.</em></>}/>The prompt action maps to splash for sharks, dispatch for robots, shockwave for
jellyfish, and throwBall for the dog. onCommand receives the submitted text;
onReady receives the scene handle.
Presets are available when you want the default copy:
import { AsciiSharkHero, AsciiRobotHero, AsciiJellyfishHero } from "ascii-things/ui";
<AsciiSharkHero options={{ palette: "paper", seed: 3 }} /><AsciiRobotHero options={{ palette: "neon", count: 6 }} /><AsciiJellyfishHero options={{ palette: "abyss", count: 4 }} />AsciiDogHero remains available with its own dog-specific copy and ball interaction.
Compose your own layout
Section titled “Compose your own layout”The dog hero is only a recipe. Every piece is exported separately.
import { useState } from "react";import { AsciiDog } from "ascii-things/react";import { AsciiPrompt, AsciiReadout, AsciiSceneTabs, AsciiStage,} from "ascii-things/ui";import "ascii-things/ui.css";import type { PaletteName } from "ascii-things";
const PALETTES = [ { id: "abyss", label: "Abyss", meta: "night" }, { id: "paper", label: "Paper", meta: "day" }, { id: "neon", label: "Neon", meta: "loud" }, { id: "mono", label: "Mono", meta: "quiet" },] as const;
export function Yard() { const [palette, setPalette] = useState<PaletteName>("abyss");
return ( <div> <AsciiSceneTabs scenes={PALETTES.map((item) => ({ ...item }))} value={palette} onChange={(id) => setPalette(id as PaletteName)} />
<AsciiStage label="ROBOT DOG / TEST YARD" status="LIVE" aspectRatio="4 / 3" footer={ <AsciiReadout items={[ { label: "SCENE", value: "DOG" }, { label: "PALETTE", value: palette.toUpperCase() }, ]} /> } > <AsciiDog palette={palette} seed={7} /> </AsciiStage>
<AsciiPrompt label="Tell the dog what to fetch" placeholder="Throw the ball…" actionLabel="Throw" status="Awaiting command" onSubmit={(command) => console.log(command)} /> </div> );}- CELLS
- —
- UNITS
- —
- STATE
- —
- FPS
- —
Click to throw the ball, or type a command below.
Seed 7, columns 110, and speed 1 are fixed in this demo. The palette and calm controls update the running engine without rebuilding the world.
Theme tokens
Section titled “Theme tokens”The UI layer uses CSS variables, not Tailwind. Set them on :root or on a wrapper:
:root { --ascii-accent: #ff7a1a; --ascii-bg: #070809; --ascii-panel: #0b0d0f; --ascii-ink: #ece8df; --ascii-muted: rgba(236, 232, 223, 0.56); --ascii-faint: rgba(236, 232, 223, 0.32); --ascii-line: rgba(236, 232, 223, 0.14); --ascii-line-soft: rgba(236, 232, 223, 0.08); --ascii-accent-soft: rgba(255, 122, 26, 0.14); --ascii-font: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace; --ascii-radius: 0;}These variables style the frame, prompt, readout, tabs, and hero. They do not recolor canvas glyphs. Canvas colors come from the scene palette. See Theming.
Using your own design system
Section titled “Using your own design system”The UI layer is intentionally plain. If you already have a design system:
- Keep
AsciiStagefor the frame and aspect ratio, and pass your own controls throughoverlayor below it. - Use your own
InputandButtoninstead ofAsciiPrompt. - Keep
AsciiReadoutas a small<dl>or render stats in your own layout. - Use
AsciiDogdirectly if you do not need the frame at all.
Because the components are copy-paste friendly React source, they work beside shadcn/ui, Radix, or a hand-rolled component set without adapter code.