Avatars

A deterministic mesh-gradient avatar for any seed, rendered on a <canvas>, or a crisp ordered dither of the same palette via pattern. The same seed always yields the same avatar, with no stored images and no network. Self-contained: the engine is bundled in.

Installation

Install with your package manager of choice. React 18 or newer is the only peer dependency.

pnpm add @outpacelabs/avatars

Usage

Import the component and give it a seed. That's it: the same seed always renders the same gradient, so a user id or email is a stable avatar with nothing to store.

import { GradientAvatar } from "@outpacelabs/avatars";

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

Props

seedstring | numberAny value; each unique seed is a unique gradient.
sizenumberRendered size in pixels. Also sets the level of detail: a small avatar is drawn with fewer colors and fewer, larger shapes, a big one gets the full complexity. Default 32.
pattern"mesh" | "dither"Render engine. mesh is the soft gradient; dither is an ordered dither of the same palette. Default "mesh".
radiusnumber | stringCorner radius. Number = pixels, string = any CSS length. Pass 0 for a square. Default "9999px".
colorsstring[]Your own hex palette instead of the seed-derived harmony. The seed still drives the layout, so each seed stays unique but on-brand.
p3booleanRender in the Display P3 wide-gamut color space, more vivid on capable screens, unchanged on the rest. Default false.
classNamestringExtra classes on the wrapper span.
styleCSSPropertiesExtra inline styles merged onto the wrapper.

Sizes & shapes

size scales the avatar; radius reshapes it. It defaults to a full circle. Pass a number for a rounded square or 0 for a hard square.

circle
radius={18}
radius={0}
<GradientAvatar seed="studio" size={84} />            // circle
<GradientAvatar seed="studio" size={84} radius={18} /> // rounded square
<GradientAvatar seed="studio" size={84} radius={0} />  // square

Complexity follows the size

An avatar is drawn for the size it is shown at. A 24px avatar in a comment thread gets two colors and a few big shapes, so it reads as one clean mark instead of a muddy blob; a 160px profile picture gets the full palette and all the detail. Same seed, same avatar, just fewer parts when small. size drives this, so there is nothing to configure.

24px
32px
48px
84px
160px
// Same seed. The size decides how much detail fits.
<GradientAvatar seed="studio" size={24} />  // 2 colors, a few big shapes
<GradientAvatar seed="studio" size={160} /> // the full palette and detail

Patterns

pattern switches the render engine. mesh (the default) is the signature soft gradient; dither is an ordered dither of the same palette, a crisp retro look with no blur that ramps along a fixed diagonal, so a set of dithers reads as one family. Both are deterministic from the seed.

mesh
dither
<GradientAvatar seed="studio" size={84} />                  // mesh (default)
<GradientAvatar seed="studio" size={84} pattern="dither" /> // dither

Colors & P3

By default the palette is derived from the seed via color-harmony rules. Pass colors to use your own palette instead, 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.

// Your brand palette instead of the seed-derived harmony.
<GradientAvatar seed={user.id} colors={["#4f46e5", "#06b6d4", "#ec4899"]} />

Set p3 to render in the wide-gamut Display P3 color space. On capable screens the palette reads noticeably more vivid; everywhere else it maps back to the same sRGB color, so it's safe to leave on.

// Wide-gamut Display P3, more vivid on capable screens.
<GradientAvatar seed="studio" size={84} p3 />

Engine helpers

The bundled engine is re-exported so you can render gradients outside React: to a data URL for an <img>, a Blob for the clipboard, or a full-resolution download.

Every helper takes an options object. Pass displaySize when you draw larger than you display, for example at 256 px for a 32 px avatar: it is the on-screen size in CSS pixels, and it sets the level of detail. It defaults to the size you draw at.

drawMeshGradient(ctx, seed, size, options?)Paint the raw mesh into a 2D canvas context. The lowest-level primitive.
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 baked in.
gradientToDataURL(seed, options?) → stringRender and return a data URL. Handy for <img src>, downloads, or copy.
gradientToBlob(seed, options?) → Promise<Blob | null>Render and resolve a Blob, e.g. to write to the clipboard.
generatePalette(seed, options?) → GradientPaletteThe colors and harmony rule behind a seed. Pass colors for your own palette.
seedFromString(input) → number / toSeed(seed) → numberThe deterministic hashing used to turn any value into a numeric seed.
import { gradientToDataURL, generatePalette } from "@outpacelabs/avatars";

// A 512×512 PNG data URL, no React required.
const src = gradientToDataURL("jane@example.com", { size: 512 });

// Just the colors behind a seed.
const { colors, harmony } = generatePalette("jane@example.com");

Types are exported too: GradientPalette, Harmony, RenderOptions, and ExportOptions.

License

Free to use under the MIT license. Use it anywhere, commercial or not, no attribution required.

By Outpace Studios

Brands, interfaces, and motion for
venture-backed companies