ribbit

the manual

Documentation

Install deterministic avatars for React or Svelte, then use the framework-agnostic Canvas, SVG, PNG and WebM engine when you need more control.

Ribbit turns a string or number into the same generative mark every time. Use the component for an avatar, or use the core to paint Canvas, generate SVG and export media without storing images or making network requests.

Installation

$ bun add ribbit-canvas

Ribbit has no runtime dependencies. react >= 18 and svelte >= 5 are optional peer dependencies for their respective adapters.

Components

For an avatar, import the adapter for your UI framework and pass a stable user identifier.

React

import { RibbitAvatar } from "ribbit-canvas/react";

export function Avatar({ user }) {
  return <RibbitAvatar seed={user.id} size={40} />;
}

Svelte

The default export has the same avatar defaults.

<script>
  import RibbitAvatar from "ribbit-canvas/svelte";
</script>

<RibbitAvatar seed={user.id} size={40} />

Core renderer

Use the core directly when the mark belongs in a canvas composition rather than a component.

import { render } from "ribbit-canvas";

render(canvas, "null-frog", { size: 256, pattern: "dither" });

Props

RibbitAvatar is the small, high-level API for both adapters.

Prop Type Default Effect
seed string | number required Stable identity for the mark
size number 32 Displayed square size in CSS pixels
pattern "dither" | "glyph" | "wave" "dither" Rendering language
radius number | string "9999px" Circle, rounded square or square presentation
palette { background, ramp } moss Overrides the color system
animated boolean false Evolves the field when visible
className, style React only Applied to the clipping wrapper
class Svelte only Applied to the canvas

The component first paints a static frame. When animated is enabled, it pauses offscreen and respects prefers-reduced-motion.

Sizes & shapes

radius controls only how an adapter is displayed. It defaults to a circle; pass a number for a rounded square or 0 for a square.

<RibbitAvatar seed="studio" size={84} />
<RibbitAvatar seed="studio" size={84} radius={18} />
<RibbitAvatar seed="studio" size={84} radius={0} />

For generated files, use the core option shape: "circle". It makes PNG and SVG corners truly transparent, rather than merely clipping them in CSS.

const avatar = await toBlob("ada", {
  preset: "avatar",
  size: 512,
  shape: "circle",
});

Patterns

Pattern Character Best suited to
dither Square Bayer cells over a continuous field Avatars, pixel art, compact marks
glyph The field sampled as monospace symbols Technical identities, editorial graphics
wave Five to nine layered contours Covers, backgrounds, animated surfaces

Engine helpers

Use the core when a component is not the right output.

Helper Output Environment
render(target, seed, options?) Existing Canvas or 2D context Browser or Offscreen Canvas
toSVG(seed, options?) Standalone SVG string Browser or server
toDataURL(seed, options?) PNG data URL Browser DOM
toBlob(seed, options?) PNG Blob Browser DOM
toWebM(seed, options?) Animated WebM Blob Browser with MediaRecorder

The renderer accepts an HTMLCanvasElement, an OffscreenCanvas, or either 2D context. Passing a canvas sets its backing dimensions; passing a context preserves your transform.

import { render } from "ribbit-canvas";

const width = 320;
const height = 180;
const dpr = Math.min(window.devicePixelRatio, 2);

canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;

const ctx = canvas.getContext("2d");
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
render(ctx, "brook", { width, height, pattern: "wave" });

width and height override size. On a non-square canvas, cells and field frequencies stay proportional instead of stretching.

Exports

All export helpers share seed, pattern, shape, palette and t.

import { toBlob, toSVG } from "ribbit-canvas";

const avatar = await toBlob("ada", {
  preset: "avatar",
  size: 512,
  pattern: "glyph",
  shape: "circle",
});

const socialCover = toSVG("release-1.4", {
  preset: "og",
  pattern: "wave",
});

preset: "avatar" is square. preset: "og" always produces a native 1200×630 cover; size, width and height do not change that preset.

Try avatar and OG formats in the playground.

Animation

t: 0 is a static frame. Advance time in seconds to animate the same visual identity.

render(ctx, "dragonfly", {
  size: 256,
  pattern: "dither",
  t: performance.now() / 1000,
});

Use toWebM when an animation must leave the browser as a file.

import { PALETTES, toWebM } from "ribbit-canvas";

const video = await toWebM("dragonfly", {
  preset: "og",
  pattern: "wave",
  palette: PALETTES.tide,
  duration: 5,
  fps: 30,
});

Recording happens in real time. Ribbit tries VP9, then VP8, then generic WebM; the helper rejects when the browser cannot encode any supported format.

Palettes

Every renderer accepts a palette with a background and a dark-to-light ramp. Ribbit ships four palettes: moss, tide, ember and mono.

import { PALETTES, toSVG } from "ribbit-canvas";

const cover = toSVG("launch", {
  preset: "og",
  pattern: "wave",
  palette: PALETTES.ember,
});

Custom ramps need at least two colors. Changing a palette never changes a seed’s geometry, so the same seed and pattern retain their composition.

API reference

Option Type Default Effect
size number 200 Fallback for both dimensions
width, height number size Logical dimensions
pattern pattern name "dither" Dither, glyph or wave renderer
shape "rectangle" | "circle" "rectangle" Transparent circular crop for core output
palette palette moss Background and tonal ramp
t number 0 Time in seconds

Lower-level drawDither, drawGlyph and drawWave paint square contexts directly. seedFromString hashes strings to unsigned 32-bit seeds, and toSeed accepts either a string or number.

License

Ribbit is MIT licensed and free to use in personal and commercial projects.