Design system recipes
The React wrappers are canvases, not a design system. They accept className,
style, width, height, and aspectRatio, so the host owns the frame. The
pattern is the same everywhere: give the scene a sized box, let the canvas fill
it, and let the surrounding library own color, spacing, and state.
shadcn/ui
Section titled “shadcn/ui”shadcn/ui copies components into your project. Import them from
@/components/ui/... as usual, and put the scene inside a sized container. The
surrounding card can use Tailwind; the canvas does not need it.
components/koi-card.tsx:
"use client";
import { AsciiKoi } from "ascii-things/react";import { Badge } from "@/components/ui/badge";import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export function KoiCard() { return ( <Card className="overflow-hidden border-white/10 bg-zinc-950 text-zinc-50"> <CardHeader> <div className="flex items-center justify-between gap-4"> <CardTitle className="text-sm font-medium">Koi pond</CardTitle> <Badge variant="secondary">live</Badge> </div> </CardHeader> <CardContent className="p-0"> <div className="h-96 bg-black"> <AsciiKoi palette="abyss" seed={12} count={5} label="A live ASCII koi pond" /> </div> </CardContent> </Card> );}The h-96 on the wrapper is the important part. AsciiKoi fills the available
height and width; without a sized parent, the canvas has nothing to fill.
Material UI
Section titled “Material UI”MUI can own the card, typography, and controls. Pass a seed from React state
and the scene remounts with a new world.
components/jellyfish-card.tsx:
"use client";
import { useState } from "react";import { AsciiJellyfish } from "ascii-things/react";import { Box, Button, Card, CardContent, CardHeader, Chip, Stack, Typography,} from "@mui/material";
export function JellyfishCard() { const [seed, setSeed] = useState(3);
return ( <Card sx={{ bgcolor: "#070809", color: "#ece8df", border: "1px solid", borderColor: "divider", }} > <CardHeader title={<Typography variant="subtitle2">Jellyfish bloom</Typography>} action={<Chip label="live" size="small" variant="outlined" />} /> <Box sx={{ height: 280 }}> <AsciiJellyfish palette="abyss" seed={seed} label="A live ASCII jellyfish bloom" /> </Box> <CardContent> <Stack direction="row" alignItems="center" justifyContent="space-between"> <Typography variant="caption" color="text.secondary"> seeded world / abyss </Typography> <Button size="small" variant="contained" onClick={() => setSeed((value) => value + 1)} > Re-seed </Button> </Stack> </CardContent> </Card> );}Install MUI alongside the package:
npm install ascii-things @mui/material @emotion/react @emotion/styledDither and WebGL surfaces
Section titled “Dither and WebGL surfaces”The ascii-things/dither and ascii-things/webgl entries are design surfaces
rather than scenes. Drop them into the same sized-box pattern.
components/dither-card.tsx:
"use client";
import { AsciiDither } from "ascii-things/dither/react";import { Card, CardContent } from "@/components/ui/card";
export function DitherCard() { return ( <Card className="overflow-hidden border-white/10 bg-zinc-950"> <CardContent className="p-0"> <div className="h-40"> <AsciiDither text="ASCII-THINGS" matrix="bayer8" density={0.12} /> </div> </CardContent> </Card> );}AsciiDither defaults to 240px; the Tailwind h-40 box overrides it. The
same pattern in MUI with the WebGL surface:
"use client";
import { AsciiSurface } from "ascii-things/webgl/react";import { Box } from "@mui/material";
export function SurfacePanel() { return ( <Box sx={{ height: 320, bgcolor: "#050607" }}> <AsciiSurface text="ASCII-THINGS" density={0.16} pointer /> </Box> );}Both entries are tree-shakeable, so a scene-only app does not pull them in. See Dither field and WebGL surface.
Tailwind, Chakra, Mantine, and friends
Section titled “Tailwind, Chakra, Mantine, and friends”The scene does not care which component library owns the box. Use a fixed
height, an aspect ratio, or the aspectRatio prop.
import { AsciiCity } from "ascii-things/react";
export function CityPanel() { return ( <div className="h-72 overflow-hidden bg-black"> <AsciiCity palette="neon" seed={8} label="A live ASCII city grid" /> </div> );}The equivalent in Chakra is a <Box h="18rem">; in Mantine, <Box h={288}>.
Any component that renders a block-level wrapper with a height works.
If you use the optional ascii-things/ui layer inside a
design system, import ascii-things/ui.css once at the app root, not inside each
card. Its components use CSS variables and can sit inside a shadcn or MUI frame.
Plain HTML, Vue, Svelte, and Astro
Section titled “Plain HTML, Vue, Svelte, and Astro”For non-React hosts, use a custom element instead of a wrapper:
<script type="module" src="/node_modules/ascii-things/dist/element.js"></script>
<ascii-city palette="neon" seed="8" label="A live ASCII city grid" style="display: block; height: 288px"></ascii-city>The host element owns the height. The canvas fills it and handles pointer input inside that box.
Shared rules
Section titled “Shared rules”- Give the canvas a height. Scenes have no intrinsic aspect ratio. Use a
fixed height,
aspectRatio, or a grid/flex track that resolves to a height. - Use
labelfor meaningful art. Without it, the canvas is hidden from assistive technology. With it, the canvas becomesrole="img"with that name. - Let the library own the frame. Use shadcn, MUI, Tailwind, or Chakra for borders, radius, spacing, and controls. The scene should not re-implement them.
- Pick a palette explicitly when needed.
palette="auto"follows the OS color scheme, not the component library’s theme. Pass"paper"for light surfaces or"abyss"/"neon"for dark ones. - Keep controls outside the canvas. Interactive scenes use pointer input inside their box. Put buttons and sliders in a separate toolbar.
- Import UI CSS once. If you use
ascii-things/ui, importascii-things/ui.cssat the app root.
See React usage for live props and remount rules, and SSR and hydration for the client-component contract.