Live demo → — try every style and trait in the browser.
Cast Avatar is a tiny, dependency-free JavaScript library for creating deterministic SVG avatars without network access. It is inspired by DiceBear and similar avatar systems that turn a stable seed into repeatable faces, initials, or geometric shapes.
- Offline-first: no remote images, fonts, APIs, or package dependencies are required.
- Deterministic: the same seed and options always generate the same SVG.
- Persistable:
avatarHash()creates a compact lookup key, whileencodeAvatar()stores the exact resolved avatar configuration. - Framework-free: use it with plain HTML, CSS, and JavaScript.
- Inclusive traits: apps can choose or auto-generate skin tone, face shape, hair, eyebrows, eye shape, nose, mouth, gender presentation, facial hair, freckles, blush, headwear, earrings, accessories, and clothing color.
npm install cast-avatarimport { createAvatar } from 'cast-avatar';
const svg = createAvatar('ada@example.com', { style: 'face' });The package ships as ES module source with no build step, and includes
TypeScript declarations (avatar.d.ts) so every option and trait is typed.
No build or install needed — cast-avatar is served from npm CDNs. Use
jsDelivr's auto-bundled, minified single file:
<script type="module">
import { createAvatar } from 'https://cdn.jsdelivr.net/npm/cast-avatar@0.1.0/+esm';
document.body.innerHTML = createAvatar('ada@example.com', { style: 'face' });
</script>Or import the raw ES module source (also works on unpkg):
<script type="module">
import { createAvatar } from 'https://cdn.jsdelivr.net/npm/cast-avatar@0.1.0/src/avatar.js';
</script>The <cast-avatar> element works the same way — importing it registers the tag:
<script type="module">
import 'https://cdn.jsdelivr.net/npm/cast-avatar@0.1.0/element/+esm';
</script>
<cast-avatar seed="ada@example.com" variant="face" size="96"></cast-avatar>Pin the version (@0.1.0) in production: an unpinned URL serves the latest
release, which can change an existing seed's avatar — see
Stability and versioning.
The snippets below import from ./src/avatar.js so they run directly from a
clone (open index.html with no build); when installed from npm, import from
cast-avatar instead.
<div id="avatar"></div>
<script type="module">
import { avatarHash, encodeAvatar, mountAvatar } from './src/avatar.js';
const options = {
seed: 'ada@example.com',
style: 'face',
traits: {
gender: 'feminine',
skinTone: 'medium',
hairStyle: 'curly',
hairColor: 'black',
eyes: 'round',
accessories: 'glasses'
}
};
mountAvatar('#avatar', options);
console.log(avatarHash(options));
console.log(encodeAvatar(options));
</script>Returns an SVG string. Use either an options object or a seed with options as the second argument.
import { createAvatar } from './src/avatar.js';
const svg = createAvatar('user-123', { style: 'shapes', size: 96 });Renders the SVG into a DOM element or selector and returns the generated SVG string.
mountAvatar(document.querySelector('[data-avatar]'), 'Lin Chen', { style: 'face' });Returns a deterministic base-36 hash for a resolved avatar configuration. Use this as a compact lookup key when you also store the seed/options somewhere else.
const hash = avatarHash({ seed: 'user-123', traits: { skinTone: 'dark', hairStyle: 'coily' } });Use these helpers when your app needs to persist the exact avatar that was generated, including all auto choices after they resolve.
const encoded = encodeAvatar({ seed: 'user-123', traits: { skinTone: 'dark' } });
const config = decodeAvatar(encoded);
const svg = createAvatar(config);Encoded strings include a version prefix and an integrity hash so tampered payloads are rejected.
Use these helpers for img tags.
const uri = createAvatarDataUri({ seed: 'user-123' });
document.querySelector('img').src = uri;Creates a browser SVGElement without mounting it.
const element = createAvatarElement({ seed: 'user-123' });
document.body.append(element);Returns the deterministic, fully-resolved option set used by the renderer. This is useful for debugging or storing the final values that auto produced.
The exported table of every allowed value for each style and trait — the canonical source for building pickers or validating input.
import { avatarOptions } from 'cast-avatar';
avatarOptions.style; // ['portrait', 'cartoon', 'minimal', 'line', ...]
avatarOptions.hairStyle; // ['none', 'stubble', 'short', 'long', ...]Render a roster in one call. createAvatars returns an array of SVG strings;
createAvatarSprite packs them into a single SVG grid (handy for previewing a
fleet of agents). Each item is a seed or an options object.
import { createAvatars, createAvatarSprite } from 'cast-avatar';
const avatars = createAvatars(['agent-1', 'agent-2'], { style: 'line' });
const sheet = createAvatarSprite(agentIds, { columns: 8, cell: 64, style: 'bot' });createAvatarGroup composes several member seeds into one cohesive mark — a
clipped mosaic (1–4 tiles), with extra members collapsing into a +N chip.
Great for a team channel, an agent squad, or a group DM icon. mergeSeeds
combines seeds into a single deterministic, order-independent seed for a
stable "pair"/relationship avatar.
import { createAvatarGroup, mergeSeeds, createAvatar } from 'cast-avatar';
const team = createAvatarGroup(['ada', 'grace', 'lin', 'noor', 'kai'], { style: 'portrait' });
// '+1' chip for the 5th member; pass { max: 4 } to tune the cap.
// Members can also be objects with per-member overrides:
const squad = createAvatarGroup([
'ada',
{ seed: 'grace', style: 'bot' },
{ seed: 'lin', traits: { skinTone: 'dark' } },
], { style: 'portrait' });
const pair = createAvatar(mergeSeeds('alice', 'bob')); // same avatar regardless of orderPass a palette to theme avatars to your own colors. Tone maps
(skinTones/hairColors) merge over the defaults; color lists
(backgrounds, shapeColors, clothingColors, inks) replace them. The
result stays fully deterministic, and the palette is preserved through
encodeAvatar/decodeAvatar.
const brand = {
backgrounds: ['#0f172a', '#1e293b'],
shapeColors: ['#22d3ee', '#a78bfa', '#f472b6'],
clothingColors: ['#0ea5e9', '#6366f1'],
};
createAvatar('agent-7', { style: 'shapes', palette: brand });For a built-in colorblind-safe option, pass the preset name instead of an object. It uses the Okabe–Ito palette (distinguishable across common color-vision deficiencies) for the abstract styles, clothing, and inks, while leaving skin and hair tones natural:
createAvatar('agent-7', { style: 'shapes', palette: 'accessible' });The preset is also exported (COLORBLIND_SAFE_PALETTE, PALETTE_PRESETS) if you
want to extend or compose it, and resolvePalette(override) resolves an object or
preset name into a full color set.
Every avatar renders as an accessible image: role="img" with an aria-label
and a native <title> (defaulting to `${seed} avatar`). Set a meaningful
title when the seed isn't human-friendly:
createAvatar('u_8f3a1', { title: 'Ada Lovelace' });When an avatar sits next to text that already names the person, mark it
decorative so assistive tech skips it (it renders aria-hidden="true" with no
role, label, or title):
createAvatar('u_8f3a1', { decorative: true });For color-vision accessibility, use palette: 'accessible' (see
Custom palettes). The status badge otherwise conveys state
through color and corner position alone — add status: { state, icon: true } for
a shape glyph so it's distinguishable without color, and pair it with visible
text where presence is important.
expression is a shorthand that presets the expressive traits
(eyes + mouth + eyebrows) while leaving identity traits seed-derived —
so the same avatar can change mood without becoming a different person. It's
ideal for reflecting user or agent state (idle, thinking, done, error). Explicit
traits still take precedence over an expression, and it applies to the face
styles (portrait, studio, cartoon, minimal, line).
createAvatar('agent-7', { expression: 'thinking' }); // same agent, pensive
createAvatar('agent-7', { expression: 'happy' }); // same agent, pleased
// per-trait overrides still win:
createAvatar('agent-7', { expression: 'happy', traits: { eyes: 'wink' } });Values: neutral, happy, sad, surprised, thinking, wink.
A zero-dependency <cast-avatar> custom element is available from the
cast-avatar/element entry point. Importing the module registers the element.
<script type="module">
import 'cast-avatar/element';
</script>
<cast-avatar seed="ada@example.com" variant="face" size="96"></cast-avatar>Attributes: seed, variant (the style name — style is reserved by HTML),
size, and background. The element re-renders when any of these change. For
full trait control, render with the JavaScript API instead.
| Option | Values |
|---|---|
seed |
Any stable string, such as a user ID, email, or username. name and id are accepted as aliases. |
style |
portrait (default), studio, cartoon, minimal, line, pixel, initials, bot, shapes, mesh; or auto for a seed-random style. face is a legacy alias for cartoon. |
expression |
neutral, happy, sad, surprised, thinking, wink — presets the expressive traits while keeping identity stable. See Expressions. |
size |
Pixel size from 24 to 1024; defaults to 128. |
background |
Any CSS color, transparent, a seeded gradient, a seeded pattern (dots, rings, grid), or auto. |
radius |
SVG corner radius — a number (px) or CSS length; defaults to 50% (circle). |
title |
Accessible label (and <title> tooltip) for the SVG; defaults to "<seed> avatar". |
decorative |
When true, hide the SVG from assistive tech (aria-hidden, no role/label/title). Defaults to false. See Accessibility. |
initials |
Optional text override for the initials style. |
status |
Presence badge. A state string (online/busy/away/offline) for a corner dot, or an object { state, shape: 'dot'|'ring', position, pulse, icon } for a ring border, custom placement/animation, or a colorblind-safe shape glyph (icon: true). Omitted = none. Applies to every style. |
animate |
A subtle looping animation: breathe, bounce, or blink (eye-blink, face styles only). Respects prefers-reduced-motion. Works on every style. |
palette |
Override the default color sets (object), or a preset name ('accessible' / 'colorblind-safe') — see Custom palettes. |
fontWeight |
Monogram font weight for the initials style (default 800). |
fontFamily |
Monogram font family for the initials style. |
traits |
Object of per-feature traits; see below. |
For the initials style, set radius: 0 for a square monogram tile or keep the
default 50% for a circle, and use fontWeight/fontFamily to restyle the text.
The styles are deterministic from the seed. portrait (the default), studio,
cartoon, minimal, line, and pixel use the trait set below — portrait
is a refined illustrative face, studio a semi-realistic, sculpted portrait of
the same person (a faceShape-driven head, multi-layer skin modelling, a sculpted
nose, volumetric lips, and lidded eyes), cartoon a playful one (formerly
face), minimal a flat geometric one, and line a monochrome thin-line one.
initials renders a monogram, and bot/shapes/mesh derive their colors and
composition from the seed alone (mesh is an abstract gradient-blob style).
Not every trait applies to every style: the illustrative faces (portrait,
studio, cartoon) honour the full set; minimal and line render a
style-appropriate subset (and line, being monochrome, ignores colour traits);
and pixel/initials/bot/shapes/mesh derive everything from the seed and
ignore face traits. The playground only shows the controls a given style uses.
Traits live under the traits key. Every trait also accepts auto (the
default), which deterministically chooses a value from the seed. Each trait is
drawn from its own seed-derived stream, so setting one trait never changes the
auto-generated value of another.
| Trait | Values |
|---|---|
gender |
neutral, feminine, masculine. |
skinTone |
light, mediumLight, medium, mediumDark, dark. |
faceShape |
round, oval, soft. |
hairStyle |
none, stubble, short, long, curly, coily, bun, afro, mohawk, spiky, hijab. |
hairColor |
black, brown, blonde, red, gray, white. |
eyebrows |
flat, raised, angled. |
eyes |
round, smile, sleepy, wink. |
nose |
soft, button, wide. |
mouth |
smile, neutral, open. |
facialHair |
none, stubble, mustache, goatee, beard, fullBeard, sideburns. |
freckles |
none, light, heavy. |
blush |
none, soft. |
headwear |
none, beanie, cap, turban, bucket, hijab. |
earrings |
none, studs, hoops. |
accessories |
none, glasses, sunglasses. |
clothing |
Any CSS color or auto; colors the shoulders/collar in the face styles. The one free-color trait. |
The face style uses every trait. The pixel style derives a comparable
character (skin, hair, hat, glasses, beard) from the seed. A hijab hairstyle
implies hijab headwear unless you set a different headwear explicitly.
The legacy top-level hair and clothing options are still accepted as aliases for traits.hairStyle and traits.clothing.
The complete, machine-readable list of every allowed value lives in the
exported avatarOptions table — see below.
createAvatar — and createAvatars, createAvatarSprite, createAvatarDataUri,
avatarHash, encodeAvatar — are pure string functions with no DOM or Node
APIs, so they run anywhere: Node, Deno, Bun, Cloudflare Workers, and Vercel /
Netlify Edge. And because Cast is dependency-free, there's nothing to bundle.
// Cloudflare Workers / any fetch handler
import { createAvatar } from 'cast-avatar';
export default {
fetch(request) {
const seed = new URL(request.url).searchParams.get('seed') || 'cast';
const svg = createAvatar(seed, { style: 'portrait' });
return new Response(svg, {
headers: {
'content-type': 'image/svg+xml; charset=utf-8',
'cache-control': 'public, max-age=31536000, immutable',
},
});
},
};For Open Graph / social images, use the avatar SVG (or its data-URI from
createAvatarDataUri) directly as og:image, or rasterize it to PNG at the
edge. The output is deterministic, so a CDN can cache it forever.
(createAvatarElement and mountAvatar are the only DOM-bound helpers — not
needed for edge/SSR.)
Avatars are deterministic: the same seed and options always produce the same
SVG for a given version of cast-avatar. Because each trait is drawn from its
own seed-derived stream, rendering at any size yields the same character —
just scaled.
Across versions, the visual output for a given seed can change — adding a
hairstyle, adjusting a palette, or refining how a feature is drawn all shift
what auto resolves to or how it is rendered. This project treats a visual
change to an existing seed as a breaking change (a major version bump). If you
need an avatar to stay identical over time, pick a guarantee level:
| Need | Persist | Render with |
|---|---|---|
| Stable while you control the version | the seed (e.g. an agent/user id) | createAvatar(seed) — pin the exact version |
| Survive library upgrades | encodeAvatar(seed, options) |
createAvatar(decodeAvatar(stored)) |
| Pixel-locked forever | the rendered SVG string | use it directly |
avatarHash() is a one-way lookup/cache key. It cannot regenerate an avatar and
it changes whenever any option (including size or background) changes, so
don't use it as your identity anchor — persist the seed (or one of the artifacts
above) instead.
Try the hosted demo at go4cas.github.io/cast,
or run it locally: open index.html in a browser (serve it over HTTP so the ES
module imports resolve — e.g. npx serve or python3 -m http.server). It needs
no build step.
The demo includes:
- A large live preview with controls for every
facetrait plus size, background, and clothing. - A Randomize button, Copy SVG / Copy data-URI / Download PNG buttons, and a Copy link button.
- A shareable URL: the current avatar is encoded into the page's URL hash, so any link reproduces the exact avatar.
- A fixed-seed variation gallery for the
face,portrait,minimal,line,initials,shapes,pixel,bot, andmeshstyles.
The Team Builder (team.html) turns a
pasted list of identifiers — names, emails, or IDs, with optional labels — into a
whole set of avatars at once. Style the team, customize individuals (style,
expression, status, and full identity traits), reorder or remove tiles, and
import a roster from CSV. Export the result as a ZIP of SVGs or PNGs, a sprite
sheet, a group mark, a createAvatars snippet, or a shareable link. Like the
rest of the demo it's a single static file with no build step.
Run the test suite with:
npm testCast Avatar was inspired by DiceBear — its
seed-driven, multi-style approach to avatars shaped this project's design, and
the pixel and bot styles in particular take cues from DiceBear's catalog.
All of Cast Avatar's artwork is original and procedurally generated; no DiceBear
assets are bundled or copied.