React Component

TabbiedArtwork renders a Tabbied generative artwork into a normal, CSS-sizeable box — like an <img> — powered by css-doodle. Point it at any preset (or your own definition), size it with CSS, reseed it, and export it to PNG.

Install

The component ships in the tabbied package. React is an optional peer dependency — you only need it for the tabbied/react entry point.

npm install tabbied

Basic usage

Import the component and a preset, then render it inside a sized box. On the server and the first client paint it shows the artwork's background color (correct size, zero layout shift); the live pattern takes over once it mounts.

import { TabbiedArtwork } from 'tabbied/react';
import { radius } from 'tabbied/artworks';

export function Banner() {
  return (
    <TabbiedArtwork
      artwork={radius}
      seed="k9Pz"
      fit="cover"
      style={{ width: '100%', height: 320 }}
    />
  );
}

Fit modes

The fit prop controls how the artwork relates to its box:

  • grid (default) adapts the cell grid to the measured container.
  • stretch keeps the authored grid and stretches it to fill.
  • cover / contain scale a fixed-resolution render (preserving fixed-px effects) to fill or letterbox the box.
  • fixed renders at explicit width / height props.

Each artwork also declares a sensible default, so fit is optional.

fit="grid"
fit="cover"
fit="contain"
// grid (default): the cell grid adapts to the container size
<TabbiedArtwork artwork={radius} fit="grid" />

// cover: a fixed-resolution render is scaled to fill the box
<TabbiedArtwork artwork={radius} fit="cover" />

// contain: letterboxed at the artwork's authored ratio
<TabbiedArtwork artwork={symmetry} fit="contain" />

// stretch keeps the authored grid; fixed renders at width/height props

Custom palette

Pass palette to recolor a design — the background color (color0) comes first. Unspecified slots fall back to the preset's palette.

<TabbiedArtwork
  artwork={radius}
  seed="k9Pz"
  // color0 (the background) comes first
  palette={['#0b132b', '#5bc0be', '#6fffe9', '#ff6b6b']}
  fit="cover"
  style={{ width: '100%', height: 280 }}
/>

Options

Every preset exposes adjustable options — the same controls the Tabbied editor shows. Pass them keyed by option id; anything you omit uses the authored default.

// Option ids come from the preset (the same controls the editor shows).
// Radius takes a grid size, a shape frequency, and a shadow toggle.
<TabbiedArtwork
  artwork={radius}
  seed="k9Pz"
  options={{ grid: '4x6', shadow: true }}
  fit="cover"
  style={{ width: '100%', height: 280 }}
/>

Reseed & export

Grab a ref to the component's handle to drive it imperatively: redraw() re-randomizes the seed (designs with CSS transitions morph between variations), and exportImage() saves a PNG. Try it:

import { useRef } from 'react';
import { TabbiedArtwork, type TabbiedArtworkHandle } from 'tabbied/react';
import { radius } from 'tabbied/artworks';

export function Reseedable() {
  const ref = useRef<TabbiedArtworkHandle>(null);

  return (
    <>
      <TabbiedArtwork ref={ref} artwork={radius} fit="cover" />
      <button onClick={() => ref.current?.redraw()}>Redraw</button>
      <button onClick={() => ref.current?.exportImage()}>Export PNG</button>
    </>
  );
}

Ambient animation

Set redrawIntervalto reseed on a timer — the gallery's shimmer. It pauses while the tab is hidden (or when paused is set) and is skipped entirely under prefers-reduced-motion.

// Reseed on a timer (the gallery's shimmer). Paused while the tab is
// hidden, and skipped entirely under prefers-reduced-motion.
<TabbiedArtwork
  artwork={quilt}
  fit="cover"
  redrawInterval={2000}
  style={{ width: '100%', height: 280 }}
/>

Props

PropTypeDescription
artworkArtworkDefinitionThe artwork to render. Import a preset from tabbied/artworks or pass your own definition. Required.
seedstringPattern seed. Omit for a random seed per mount; reseed via the handle.
palettestring[]Active colors, background (color0) first. Defaults to the preset palette.
optionsRecord<string, OptionValue>Option values keyed by option id; unset options use authored defaults.
fit'grid' | 'stretch' | 'cover' | 'contain' | 'fixed'How the artwork fills its box. Defaults per artwork.
cellSizenumberfit="grid" — target cell size in px (default 36).
density0 | 1 | 2 | 3 | 4fit="grid" — authored density level, an alternative to cellSize.
width / heightnumberfit="fixed" — canvas size in px.
coverRender{ width, height, cropTop? }cover/contain render resolution override.
redrawIntervalnumberRe-randomize the seed every N ms (uncontrolled seed only).
pausedbooleanPause redrawInterval ticks without resetting the timer.
decorativebooleantrue (default) renders an aria-hidden image; false exposes role="img" with ariaLabel.
onReady() => voidCalled once the first pattern render is committed.
className / stylestring / CSSPropertiesApplied to the wrapper box.

See the inline JSDoc on TabbiedArtworkProps for the full list.

Handle (ref)

A ref exposes a TabbiedArtworkHandle:

MemberDescription
redraw(seed?: string)Re-randomize (or set) the seed, animating designs with CSS transitions.
exportImage(options?)PNG export via css-doodle. Returns a promise.
elementThe raw <css-doodle> element, for power users.

Importing presets

artwork takes an ArtworkDefinition. Each preset is a side-effect-free named export, so importing only the ones you render keeps unused designs out of your bundle.

// Import only what you render — bundlers ship just those presets.
import { radius, symmetry } from 'tabbied/artworks';

// Building a gallery? The full record pulls in every design.
import { artworks } from 'tabbied/artworks';

Server components

TabbiedArtwork is a client component (it registers a browser custom element on import). In the Next.js App Router, render it from a client boundary, or rely on its built-in measurable placeholder — it renders a correctly-sized box on the server and hydrates without a mismatch.

Not using React?

The same engine is available framework-free as createArtwork from tabbied.

import { createArtwork } from 'tabbied';
import { radius } from 'tabbied/artworks';

const controller = createArtwork(document.querySelector('#stage'), {
  artwork: radius,
  seed: 'k9Pz',
});

controller.redraw();         // re-randomize the seed
await controller.exportImage();
controller.destroy();

Browse the source, the full prop docs, and all 84 presets on GitHub.