Skip to documentation

Docs · @illlustrations/avatars · v1.1.0

Avatars with a little character

Give your people a face that feels like them. Seeded Croods avatars for React and plain JavaScript, rendered locally as SVG.

No API keys Renders locally SVG, all the way Made to mix

Installation

One package for the React component, a plain JavaScript renderer, and the Croods collection. Import just the entry points you need.

Terminal
npm install @illlustrations/avatars

React projects also need React 18.3.1 or 19.

ImportContents
@illlustrations/avatarscreateAvatar, fromJSON and types. No React.
@illlustrations/avatars/reactThe Avatar component (React 18.3+ or 19).
@illlustrations/avatars/croodsThe Croods style. Future styles get their own path.
@illlustrations/avatars/croods/presets.json24 resolved preset states.
@illlustrations/avatars/svg/croods-001.svgPreset SVG files, 001 to 024.

More styles are on the way. Each one gets its own import path next to /croods, in the same package.

React

Import the component and the collection. Give it a seed—like a user ID—and it will pick a repeatable character for that style version.

UserAvatar.tsx
import { Avatar } from '@illlustrations/avatars/react';
import { croods } from '@illlustrations/avatars/croods';

export function UserAvatar() {
  return (
    <Avatar
      assets={croods}
      seed="alex"
      size={64}
      shape="circle"
      background="#EDEDFF"
      title="Alex's avatar"
    />
  );
}
seed="alex"
seed="sam"
seed="jules"
seed="robin"

A different seed, a different possibility. Use title or an accessible label for meaningful avatars; otherwise the component is decorative.

Using Next.js or server rendering?

The package’s React entry point declares 'use client' and uses React’s useId for safe SVG IDs. You can import it in a Next.js page. Put interactive controls in a Client Component. If your application has multiple React roots, give each root its own matching server/client identifierPrefix.

Plain JavaScript

Use the core renderer for HTML, server-side generation, or another framework. The core and collection imports don’t load React or fetch artwork.

avatar.ts
import { createAvatar } from '@illlustrations/avatars';
import { croods } from '@illlustrations/avatars/croods';

const avatar = createAvatar(croods, {
  seed: 'alex',
  size: 64,
  shape: 'circle',
  background: '#EDEDFF',
});

// Use an image in your page. No React required.
const image = document.createElement('img');
image.src = avatar.toDataUri();
image.alt = "Alex's avatar";
document.body.append(image);

For server-side output, use avatar.toString(). When inserting multiple inline SVG strings into one page, pass a unique idPrefix to each toString() call.

Seeds

A seed picks one combination from a frozen, versioned pool. The same seed and style version give the same avatar on the server, the client and every device, so you don’t need to store anything for generated profiles.

TSX · seeds
// Same seed, same avatar: on the server, the client and every device.
<Avatar assets={croods} seed={user.id} />

// Seeds pick head, face, outfit and glasses. Facial hair is opt-in.
<Avatar assets={croods} seed={user.id} selections={{ facialHair: 'beard' }} />

// Explicit selections always win over the seed.
<Avatar assets={croods} seed={user.id} selections={{ head: 'bun', face: 'happy' }} />
seed="maya"
seed="maya" again
+ facialHair: 'beard'
+ head: 'bun'

Use a stable ID

Seed with a user ID, not a display name or Math.random(). Names change; random seeds change on every render and cause hydration mismatches.

Facial hair is opt-in

Seeds pick hair, face, outfit and glasses, but never facial hair. Add it with selections when someone chooses it.

Resolution order: style defaults → seeded combination → explicit selections. Different seeds can land on the same avatar.

Playground

Start with a seed, then choose the hair, outfit, colors, and shape. The preview and code update together. Everything here runs in your browser.

LIVE PLAYGROUNDv1.1.0 · croods style 1.0.0
Your avatar preview
Your next main character.
Shape
Theme
Colors
HairOriginal
SkinOriginal
ClothingOriginal
OutlineOriginal
Background
Artwork colors

Every color in this avatar, like the builder's palette. Roles above win over these.

Face & accessories
Your avatar · TSX
<Avatar
  assets={croods}
  seed={"hello-world"}
  background="#EDEDFF"
  shape="rounded"
  size={128}
  title="Your avatar"
/>

51 parts, with their original placement built in. Explicit choices override the seed. Choose From seed to let the package pick again.

Parts

These are the only valid IDs for selections. Click one to copy it. Unknown IDs throw, so a typo never quietly renders the wrong person.

selections.head · required

TypeScript · read parts at runtime
import { croods } from '@illlustrations/avatars/croods';

const hairstyles = croods.parts.head.map(({ id, name }) => ({
  value: id,
  label: name ?? id,
}));

Building tooling or prompting an AI? The same list is at /docs/parts.json.

Colors

Three layers, applied in order: a theme recolors everything, a palette swaps any artwork color, and the four colors roles always win.

Roles

hair, skin, clothing and stroke take one color, or a list the seed picks from.

Palette

Swap any color in the artwork by its hex, like the builder: the bag, mouth, drool and other accents.

Your brand colors, one per person

Pass a list and the seed picks one entry. Each person keeps their color everywhere, and a team gets your whole palette.

TSX · color lists
// One color, or a list: the seed picks one entry per person, every time.
<Avatar
  assets={croods}
  seed={user.id}
  colors={{ clothing: ['#254CE9', '#F76549', '#77E87B'], hair: ['original', '#272727'] }}
  background={['#DDE5FF', '#FFE6D2', '#D4EDE2']}
/>
maya
leo
priya
sam
noor
theo
jules
alex

Any artwork color

TypeScript · palette
import { createAvatar } from '@illlustrations/avatars';
import { croods } from '@illlustrations/avatars/croods';

croods.palette;          // every artwork color: ['#000000', '#FF0000', '#FFFFFF', …]

const avatar = createAvatar(croods, {
  selections: { upperBody: 't-shirt-bag' },
  colors: { hair: '#254CE9' },          // roles first
  palette: { '#FF4B33': '#254CE9' },    // then any artwork color: here, the bag
});

avatar.sourceColors();   // artwork colors in this avatar's parts
  • #000000Outlines (stroke role)
  • #FF0000Hair and beard (hair role)
  • #FFFFFFSkin (skin role), eye whites and teeth
  • #313130Dark garments (clothing role)
  • #FF4B33Bag on t-shirt-bag
  • #FFE900Yellow garments (clothing role), shirt under the blazer
  • #FF3F3FMouth and tongue
  • #424242Mouth shadow
  • #5DECFFDrool
  • #F4F1EBDetail on shirt-1

A palette entry changes every use of that color. #FFFFFF is both skin and eye whites, so change skin with colors.skin. Unknown colors throw.

Examples

Chats, comments, leaderboards, onboarding and all the little places people show up. Every face below is the same <Avatar> component, with the props under each widget.

Looks good at every size.

32px
48px
64px
96px
128px
TSX · sizing
<Avatar assets={croods} seed="alex" size={32} />
<Avatar assets={croods} seed="alex" size={64} />
<Avatar assets={croods} seed="alex" size={128} />

Recipes

Small, complete patterns for the places avatars usually end up.

Profile picture fallback

Show the uploaded photo when there is one, and a stable avatar when there isn't.

UserPhoto.tsx
import { Avatar } from '@illlustrations/avatars/react';
import { croods } from '@illlustrations/avatars/croods';

export function UserPhoto({ user }: { user: { id: string; name: string; photoUrl?: string } }) {
  if (user.photoUrl) return <img src={user.photoUrl} alt={user.name} width={40} height={40} />;
  // Seed with the user ID, not the name: names change, IDs don't.
  return <Avatar assets={croods} seed={user.id} size={40} shape="circle" title={user.name} />;
}

Member list

One theme and shape for a calm, consistent list. Decorative avatars need no title when the name is next to them.

Members.tsx
import { Avatar } from '@illlustrations/avatars/react';
import { croods } from '@illlustrations/avatars/croods';

export function Members({ members }: { members: { id: string; name: string }[] }) {
  return (
    <ul>
      {members.map(member => (
        <li key={member.id}>
          <Avatar assets={croods} seed={member.id} size={32} shape="circle" theme="neutral" background="#F2F0EE" />
          {member.name}
        </li>
      ))}
    </ul>
  );
}

Let people choose their avatar

Build a picker from the style's part list and save the resolved state. A seed alone can't restore explicit choices.

AvatarPicker.tsx
'use client';
import { useState } from 'react';
import { createAvatar, type AvatarState } from '@illlustrations/avatars';
import { Avatar } from '@illlustrations/avatars/react';
import { croods } from '@illlustrations/avatars/croods';

export function AvatarPicker({ userId, onSave }: { userId: string; onSave: (state: AvatarState) => void }) {
  const [head, setHead] = useState<string>();
  const options = { seed: userId, selections: head ? { head } : {} };
  return (
    <>
      <Avatar assets={croods} {...options} size={120} title="Your avatar" />
      <select value={head ?? ''} onChange={event => setHead(event.target.value || undefined)}>
        <option value="">Surprise me</option>
        {croods.parts.head.map(part => <option key={part.id} value={part.id}>{part.name ?? part.id}</option>)}
      </select>
      <button onClick={() => onSave(createAvatar(croods, options).toJSON())}>Save</button>
    </>
  );
}

// Later, render the saved state:
// <Avatar assets={croods} selections={saved.selections} colors={saved.colors}
//   theme={saved.theme} background={saved.background} shape={saved.shape} />

An avatar URL (Next.js route)

Serve avatars as cacheable image URLs, for places that only take a src.

app/avatars/[id]/route.ts
import { createAvatar } from '@illlustrations/avatars';
import { croods } from '@illlustrations/avatars/croods';

export async function GET(_request: Request, { params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const svg = createAvatar(croods, { seed: id, size: 256, shape: 'circle' }).toString({ title: 'Avatar' });
  return new Response(svg, {
    headers: { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=86400' },
  });
}

Same seed and style version always give the same image, so these URLs cache well.

Server-rendered HTML

Several inline SVG strings on one page need unique ID prefixes, or their masks clash.

render.ts
import { createAvatar } from '@illlustrations/avatars';
import { croods } from '@illlustrations/avatars/croods';

// idPrefix: starts with a letter; letters, digits, _ and - only; unique on the page.
// Clean the ID and add the index, so odd characters or repeated users never clash.
const prefix = (id: string, index: number) => `avatar-${index}-${id.replace(/[^A-Za-z0-9_-]/g, '')}`;

const html = users.map((user, index) =>
  createAvatar(croods, { seed: user.id, size: 40 })
    .toString({ idPrefix: prefix(user.id, index), title: user.name })
).join('');

<Avatar> and toDataUri() handle IDs for you. Only inline toString() output needs idPrefix. Seed with the raw ID; only the prefix needs cleaning.

PNG for email and social images

Many email clients and social cards don't render SVG. Convert on the server.

avatar-png.ts
import sharp from 'sharp';
import { createAvatar } from '@illlustrations/avatars';
import { croods } from '@illlustrations/avatars/croods';

const svg = createAvatar(croods, { seed: 'alex', size: 512, background: '#EDEDFF' }).toString();
const png = await sharp(Buffer.from(svg)).png().toBuffer();

API reference

Both renderers share the same avatar options. Start with the defaults and change only what you need.

OptionDefaultWhat it does
assetsAvatarStyleRequiredThe imported style, such as croods. React prop; for createAvatar it is the first argument.
seedstringDefault characterRepeatable part selection. Same seed and style version give the same result; different seeds can share a result. Seeds never add facial hair.
selectionsobjectFrom seedOverride head, face, upperBody, facialHair or accessories. Optional slots accept 'none'.
colorsobjectOriginal artworkOverride hair, skin, clothing or stroke. Hex, 'transparent' or 'original', or a list the seed picks from.
theme'ink' | 'neutral'Original artwork'ink' uses #0040FC and white; 'neutral' uses black and white. Explicit colors override theme roles.
paletteobjectNoneSwap any artwork color, like the builder: { '#FF4B33': '#254CE9' }. Keys come from croods.palette. Roles win over it; it wins over theme accents.
backgroundstring | string[]'transparent'A hex color or 'transparent', or a list the seed picks from. Separate from the artwork palette.
sizenumber64 React / 600 coreWidth and height in pixels. Above 0, at most 8192.
shapestring'square''square', 'rounded' or 'circle'.
seedPoolstring'v1'The versioned list of seeded combinations. Only needed to pin results across versions.
titlestringDecorativeAccessible name (React). Also accepts ARIA, className, style and SVG presentation props.

Choose your parts

head and upperBody are required slots. face, facialHair, and accessories also accept 'none'.

Keep the original color

Color roles default to 'original'. Override only the roles you want. Hex colors can use 3, 4, 6, or 8 digits.

createAvatar(style, options) returns toString({ idPrefix?, title? }), toDataUri({ title? }) and toJSON(). fromJSON(style, state) validates and restores saved state.

When something throws

Invalid input fails loudly instead of rendering something unexpected.

ErrorCause
Unknown head part: curly-hairA part ID that isn't in the style. Use an ID from the parts list.
Unknown slot: hairA selection key that isn't a slot. Slots are head, face, upperBody, facialHair and accessories.
Invalid hair color: use a hex color, transparent or originalNamed CSS colors like 'red' are not accepted.
Size must be greater than 0 and at most 8192Size out of range.
idPrefix must start with a letter…toString({ idPrefix }) got an ID starting with a digit or containing other characters.
Avatar state schema or style version does not matchfromJSON got state saved with a different style version.
Unknown palette color: #ABCDEF is not in the croods artworkA palette key that the artwork never uses. Pick keys from croods.palette or avatar.sourceColors().

Save & export

Save a seed for generated profiles. When someone makes an avatar their own, save the resolved JSON so their part choices and colors come back with them.

TypeScript · save & restore
import { createAvatar, fromJSON } from '@illlustrations/avatars';
import { croods } from '@illlustrations/avatars/croods';

const avatar = createAvatar(croods, {
  seed: 'alex',
  selections: { head: 'straight-long', accessories: 'none' },
  colors: { hair: '#254CE9' },
});

// Store the resolved parts, colors, and style version.
const saved = JSON.stringify(avatar.toJSON());

// Restore later using the matching style version.
const restored = fromJSON(croods, JSON.parse(saved));
const svg = restored.toString();

Saved state includes the style and schema versions. Restore with the matching style version; mismatches are rejected rather than silently changing the avatar.

Just need one SVG?

The package includes 24 ready-made portraits: croods-001.svg through croods-024.svg. Import them with your bundler, or copy them into your public assets.

Vite · static SVG
import avatarUrl from '@illlustrations/avatars/svg/croods-001.svg?url';

const image = document.createElement('img');
image.src = avatarUrl;
image.alt = 'Croods avatar';
document.body.append(image);

The ?url suffix is Vite-specific. In Next.js, copy the SVG to public/avatars/ and use its public URL. Package import paths aren’t browser URLs.

Use with AI

Coding agents guess part IDs and import paths. Give yours the skill instead: exact imports, every valid ID, the rules and the recipes. Then just ask it to “add avatars to the member list”.

Claude Code · this project
mkdir -p .claude/skills/illlustrations-avatars && curl -o .claude/skills/illlustrations-avatars/SKILL.md https://illlustrations.co/docs/skill.md
Claude Code · every project
mkdir -p ~/.claude/skills/illlustrations-avatars && curl -o ~/.claude/skills/illlustrations-avatars/SKILL.md https://illlustrations.co/docs/skill.md
Cursor
mkdir -p .cursor/rules && curl -o .cursor/rules/illlustrations-avatars.mdc https://illlustrations.co/docs/skill.md
Codex and other agents
curl -s https://illlustrations.co/docs/skill.md >> AGENTS.md

Claude.ai and Claude Desktop: download skill.md, save it as SKILL.md in a folder named illlustrations-avatars, zip the folder, and upload it under Settings → Capabilities → Skills.

For assistants

/llms.txt is the index. /llms-full.txt and /docs.md are these docs as Markdown.

For tools

/docs/parts.json lists every slot, part ID, default, color role, theme and shape, generated from the package.

The npm package ships the same rules in AI.md, so agents reading node_modules find them without going online.

License

The code is MIT. Croods artwork in the avatar package is CC BY 4.0—use it, remix it, and use it commercially with credit.

Attribution · Markdown
Croods v2 by [illlustrations / Vijay Verma](https://illlustrations.co),
© 2026, licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).

Retain required notices and indicate changes when sharing modified artwork. Other website collections keep their own licenses.

Go make something with a little character.Back to the playground