@outpacelabs/avatars
v0.6.0
Published
React component for generative gradient avatars. Every seed renders a deterministic mesh gradient or a crisp ordered dither, with no stored images.
Maintainers
Readme
Give it any string or number (a user id, an email, a username) and it paints a
unique, good-looking gradient on a <canvas>. The same seed always yields the
same gradient, so you get stable avatars with nothing to store and nothing to
fetch. Prefer pixels? pattern="dither" renders the same palette as a crisp
ordered dither. The engine is bundled in, so this is the only thing you install.
Install
npm i @outpacelabs/avatars # or: pnpm add / yarn add / bun addreact >= 18 is the only peer dependency. Works with React 18 and 19.
Usage
import { GradientAvatar } from "@outpacelabs/avatars";
function UserAvatar({ user }) {
return <GradientAvatar seed={user.id} size={40} />;
}That's the whole API surface for most apps. A few more:
<GradientAvatar seed="[email protected]" size={96} /> {/* circle (default) */}
<GradientAvatar seed="[email protected]" size={96} radius={16} /> {/* rounded square */}
<GradientAvatar seed="[email protected]" size={96} radius={0} /> {/* square */}
<GradientAvatar seed="[email protected]" size={96} pattern="dither" /> {/* ordered dither */}
<GradientAvatar seed={42} size={64} className="ring-2 ring-white/10" />Patterns
pattern switches the render engine. "mesh" (the default) is the soft mesh
gradient; "dither" is an ordered (Bayer 8×8) dither of the same palette, a
crisp retro look with no blur. The dither ramps along a fixed diagonal, so a set
of dithers reads as one family. Both are deterministic from the seed.
<GradientAvatar seed="studio" size={96} /> {/* mesh (default) */}
<GradientAvatar seed="studio" size={96} pattern="dither" /> {/* dither */}Complexity follows the size
An avatar is drawn for the size it is shown at. A 24 px avatar in a comment
thread gets a couple of colors and a few big shapes, so it reads as one clean
mark instead of a muddy blob; a 160 px profile picture gets the full palette
and all the detail. Same seed, same avatar, just fewer parts when small. The
size prop drives this, so there is nothing to configure.
<GradientAvatar seed="studio" size={24} /> {/* simple: 2 colors, big shapes */}
<GradientAvatar seed="studio" size={160} /> {/* full detail */}Driving the engine yourself, and drawing at a higher resolution than you
display? Pass displaySize so the engine knows the on-screen size.
drawMeshGradient(ctx, seed, 256, { displaySize: 32 });Your colors
By default the palette is derived from the seed via color-harmony rules. Pass
colors to use your own palette instead: your brand colors, a product
theme, anything. The seed still drives the layout (and rotates which color
leads), so every seed stays unique while staying on-brand. Hex in, # optional;
invalid entries are ignored and an empty list falls back to the generated
palette.
<GradientAvatar seed={user.id} colors={["#4f46e5", "#06b6d4", "#ec4899"]} />Wide-gamut P3
Set p3 to render in the Display P3 color space. On P3-capable screens
(most modern phones and laptops) the palette reads noticeably more vivid; on
everything else it maps back to the same sRGB color, so it's safe to leave on.
<GradientAvatar seed="studio" size={96} p3 />Why @outpacelabs/avatars
- Deterministic. Same seed, same gradient, every time. A user id or email is the avatar; you never store or migrate an image.
- No images, no network. Rendered at runtime on a
<canvas>. No CDN, no requests, no broken<img>links, no upload pipeline. - Tiny & zero-dependency. Under 3 kB gzipped;
reactis the only peer. - Actually pretty. Soft mesh gradients, or a crisp retro dither, not blocky identicons.
- Any size, any shape. Circles, rounded squares, hard squares: your call.
- Exports anywhere. Built-in helpers turn a seed into a data URL, a
Blob, or a full-resolution image for downloads and clipboard. - Typed. Ships with TypeScript declarations.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| seed | string \| number | None | Any value; each unique seed is a unique gradient. |
| size | number | 32 | Rendered size in pixels. Also sets the level of detail: small avatars are drawn simpler. |
| pattern | "mesh" \| "dither" | "mesh" | Render engine. mesh is the soft gradient; dither is an ordered dither of the same palette. |
| radius | number \| string | "9999px" | Corner radius. Number = pixels, string = any CSS length. Defaults to a full circle; pass 0 for a square. |
| colors | string[] | None | Your own hex palette instead of the seed-derived harmony. The seed still drives the layout. |
| p3 | boolean | false | Render in the Display P3 wide-gamut color space (more vivid on capable screens). |
| className | string | None | Extra classes on the wrapper <span>. |
| style | CSSProperties | None | Extra inline styles merged onto the wrapper. |
Beyond React: the engine
The framework-agnostic engine is re-exported, so you can generate gradients
without rendering a component, handy for an <img src>, a download button, or
a server-rendered preview.
import { gradientToDataURL, generatePalette } from "@outpacelabs/avatars";
// A 512×512 PNG data URL. Drop straight into an <img>.
const src = gradientToDataURL("[email protected]", { size: 512 });
// Just the colors behind a seed.
const { colors, harmony } = generatePalette("[email protected]");
// Custom palette + wide-gamut P3 flow through the engine too.
const brand = gradientToDataURL(user.id, {
size: 512,
colors: ["#4f46e5", "#06b6d4", "#ec4899"],
p3: true,
});Every render/palette helper takes an options object with colors?: string[]
and p3?: boolean (plus displaySize, blur, pattern, size, … where
relevant). displaySize is the on-screen size in CSS pixels and sets the level
of detail; it defaults to the size you draw at, so set it only when you render
larger than you display.
| Helper | Description |
|--------|-------------|
| drawMeshGradient(ctx, seed, size, options?) | Paint the raw mesh into a 2D canvas context. |
| drawDither(ctx, seed, size, options?) | Paint the ordered dither into a 2D canvas context. |
| renderGradient(canvas, seed, options?) | Render a seed into a canvas with the signature soft blur. |
| gradientToDataURL(seed, options?) | Render and return a data URL. |
| gradientToBlob(seed, options?) | Render and resolve a Blob (e.g. for the clipboard). |
| generatePalette(seed, options?) | The colors and harmony rule behind a seed (pass colors for your own). |
| seedFromString(input) / toSeed(seed) | The deterministic hashing that turns any value into a numeric seed. |
For P3, renderGradient/gradientTo*/<GradientAvatar> set up the P3 canvas
context for you; if you drive drawMeshGradient/drawDither on your own canvas,
create it with getContext("2d", { colorSpace: "display-p3" }).
Types GradientPalette, Harmony, PaletteOptions, DrawOptions,
RenderOptions, and ExportOptions are exported too.
Playground
Type any seed and watch the gradient at avatars.outpacestudios.com, then copy it to your clipboard or download a 2000×2000 image. Full docs at /docs.
License
MIT, free to use. By Outpace Studios.
