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-canvasRibbit 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.