# @illlustrations/avatars Seeded SVG avatars for JavaScript and React. Everything renders locally: no API key, network request or account. Croods is the first style. Version 1.1.0. Web: https://illlustrations.co/docs · Parts JSON: https://illlustrations.co/docs/parts.json · Agent skill: https://illlustrations.co/docs/skill.md ## Install ```sh npm install @illlustrations/avatars ``` React projects need React 18.3.1 or 19. | Import | Contents | | --- | --- | | `@illlustrations/avatars` | createAvatar, fromJSON and types. No React. | | `@illlustrations/avatars/react` | The Avatar component (React 18.3+ or 19). | | `@illlustrations/avatars/croods` | The Croods style. Future styles get their own path. | | `@illlustrations/avatars/croods/presets.json` | 24 resolved preset states. | | `@illlustrations/avatars/svg/croods-001.svg` | Preset SVG files, 001 to 024. | ## React ```tsx import { Avatar } from '@illlustrations/avatars/react'; import { croods } from '@illlustrations/avatars/croods'; export function UserAvatar() { return ( ); } ``` Use `title` or an accessible label for meaningful avatars; otherwise the component is decorative. The React entry declares `'use client'` and uses `useId` for unique SVG IDs, so it works in Next.js pages. Apps with several React roots need matching server/client `identifierPrefix` values. ## Plain JavaScript ```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 output use `avatar.toString()`. Multiple inline SVG strings on one page each need a unique `toString({ idPrefix })`. ## Seeds and randomness ```tsx // Same seed, same avatar: on the server, the client and every device. // Seeds pick head, face, outfit and glasses. Facial hair is opt-in. // Explicit selections always win over the seed. ``` A seed picks one combination from a frozen, versioned pool (`v1`), so the same seed and style version always give the same avatar. Different seeds can share an avatar. Seeds never add facial hair; select it explicitly. Resolution order: style defaults → seeded combination → explicit `selections`. ## Parts | Slot | Required | Default | Valid IDs | | --- | --- | --- | --- | | `head` | yes | `default` | `afro-1`, `afro-2`, `bald`, `bangs`, `bowl-cut`, `braid-1`, `braid-2`, `bun-1`, `bun-2`, `bun`, `default`, `dread`, `long-hair`, `long-hair-1`, `long-hair-2`, `messy-afro`, `messy`, `mohawk`, `no-hair`, `normal`, `pixie`, `quiff-1`, `quiff-2`, `shaggy`, `short-hair-1`, `short-hair-2`, `short-hair-3`, `short-hair-4`, `short-hair-5`, `short-hair-6`, `short-hair-7`, `short-hair-8`, `short-hair-9`, `straight-long`, `trimmed`, `wavy-curls` | | `face` | no | `normal` | `drool`, `drool-2`, `happy`, `normal`, `open-mouth-1`, `sad`, `none` | | `upperBody` | yes | `t-shirt-1` | `blazer`, `hoodie-1`, `shirt-1`, `t-shirt-1`, `t-shirt-bag` | | `facialHair` | no | `none` | `beard`, `moustache`, `stubble`, `none` | | `accessories` | no | `none` | `glass`, `none` | Seeds pick every slot except `facialHair`, which stays `none` unless you select it. Read them at runtime: ```ts import { croods } from '@illlustrations/avatars/croods'; const hairstyles = croods.parts.head.map(({ id, name }) => ({ value: id, label: name ?? id, })); ``` ## Colors Three layers, applied in this order: 1. `theme`: `'ink'` or `'neutral'` recolors everything. 2. `palette`: swap any artwork color by its hex, like the website builder. 3. `colors`: the four roles (`hair`, `skin`, `clothing`, `stroke`) always win. ### Color lists Any role and `background` take a list; the seed picks one entry per person: ```tsx // One color, or a list: the seed picks one entry per person, every time. ``` ### Palette ```ts 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 ``` | Artwork color | What it paints | | --- | --- | | `#000000` | Outlines (stroke role) | | `#FF0000` | Hair and beard (hair role) | | `#FFFFFF` | Skin (skin role), eye whites and teeth | | `#313130` | Dark garments (clothing role) | | `#FF4B33` | Bag on t-shirt-bag | | `#FFE900` | Yellow garments (clothing role), shirt under the blazer | | `#FF3F3F` | Mouth and tongue | | `#424242` | Mouth shadow | | `#5DECFF` | Drool | | `#F4F1EB` | Detail on shirt-1 | A palette entry changes every use of that color, so change skin with `colors.skin` rather than `#FFFFFF` (which also paints eye whites). Unknown colors throw. ## API reference | Option | Type | Default | Description | | --- | --- | --- | --- | | `assets` | AvatarStyle | Required | The imported style, such as croods. React prop; for createAvatar it is the first argument. | | `seed` | string | Default character | Repeatable part selection. Same seed and style version give the same result; different seeds can share a result. Seeds never add facial hair. | | `selections` | object | From seed | Override head, face, upperBody, facialHair or accessories. Optional slots accept 'none'. | | `colors` | object | Original artwork | Override 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. | | `palette` | object | None | Swap any artwork color, like the builder: { '#FF4B33': '#254CE9' }. Keys come from croods.palette. Roles win over it; it wins over theme accents. | | `background` | string \| string[] | 'transparent' | A hex color or 'transparent', or a list the seed picks from. Separate from the artwork palette. | | `size` | number | 64 React / 600 core | Width and height in pixels. Above 0, at most 8192. | | `shape` | string | 'square' | 'square', 'rounded' or 'circle'. | | `seedPool` | string | 'v1' | The versioned list of seeded combinations. Only needed to pin results across versions. | | `title` | string | Decorative | Accessible name (React). Also accepts ARIA, className, style and SVG presentation props. | `createAvatar(style, options)` returns `{ toString(options?), toDataUri(options?), toJSON() }`. `toString` accepts `{ idPrefix?, title? }`; `toDataUri` accepts `{ title? }`. `fromJSON(style, state)` validates and restores saved state. ### Errors Invalid input throws instead of rendering something unexpected. | Error | Cause | | --- | --- | | `Unknown head part: curly-hair` | A part ID that isn't in the style. Use an ID from the parts list. | | `Unknown slot: hair` | A selection key that isn't a slot. Slots are head, face, upperBody, facialHair and accessories. | | `Invalid hair color: use a hex color, transparent or original` | Named CSS colors like 'red' are not accepted. | | `Size must be greater than 0 and at most 8192` | Size 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 match` | fromJSON got state saved with a different style version. | | `Unknown palette color: #ABCDEF is not in the croods artwork` | A palette key that the artwork never uses. Pick keys from croods.palette or avatar.sourceColors(). | ## Save and restore ```ts 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. Mismatches are rejected rather than silently changing the avatar. ### Static SVG files The package includes 24 portraits, `croods-001.svg` to `croods-024.svg`: ```ts 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); ``` `?url` is Vite-specific. In Next.js, copy the SVG to `public/` and use its URL. ## Recipes ### Profile picture fallback Show the uploaded photo when there is one, and a stable avatar when there isn't. ```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 {user.name}; // Seed with the user ID, not the name: names change, IDs don't. return ; } ``` ### Member list One theme and shape for a calm, consistent list. Decorative avatars need no title when the name is next to them. ```tsx import { Avatar } from '@illlustrations/avatars/react'; import { croods } from '@illlustrations/avatars/croods'; export function Members({ members }: { members: { id: string; name: string }[] }) { return (
    {members.map(member => (
  • {member.name}
  • ))}
); } ``` ### 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. ```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(); const options = { seed: userId, selections: head ? { head } : {} }; return ( <> ); } // Later, render the saved state: // ``` ### An avatar URL (Next.js route) Serve avatars as cacheable image URLs, for places that only take a src. ```tsx 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. ```tsx 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(''); ``` 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. ```tsx 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(); ``` ## Use with coding agents Claude Code · this project: ```sh mkdir -p .claude/skills/illlustrations-avatars && curl -o .claude/skills/illlustrations-avatars/SKILL.md https://illlustrations.co/docs/skill.md ``` Claude Code · every project: ```sh mkdir -p ~/.claude/skills/illlustrations-avatars && curl -o ~/.claude/skills/illlustrations-avatars/SKILL.md https://illlustrations.co/docs/skill.md ``` Cursor: ```sh mkdir -p .cursor/rules && curl -o .cursor/rules/illlustrations-avatars.mdc https://illlustrations.co/docs/skill.md ``` Codex and other agents: ```sh curl -s https://illlustrations.co/docs/skill.md >> AGENTS.md ``` ## License Code: MIT. Croods artwork: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/); commercial use is fine with credit: ```md Croods v2 by [illlustrations / Vijay Verma](https://illlustrations.co), © 2026, licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). ``` --- # Agent rules 1. Use only part IDs from the parts table (or /docs/parts.json). Unknown IDs throw. `head` and `upperBody` must be real IDs; `face`, `facialHair` and `accessories` also accept `'none'`. 2. Seed with a stable ID (user ID), never a display name or `Math.random()`, so the avatar is identical on server and client and across sessions. 3. When users choose parts, save `createAvatar(...).toJSON()` and restore with `fromJSON` or by passing `selections`, `colors`, `theme`, `background` and `shape` back. A seed alone cannot restore explicit choices. 4. In React, pass the style as `assets={croods}`. `style` is the normal React CSS prop. 5. Several inline `toString()` SVGs on one page need unique `idPrefix` values (letter first; letters, digits, `_`, `-`). `` and `toDataUri()` don't. 6. Colors: `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, `'transparent'`, or `'original'` for color roles. Named CSS colors throw. 7. `` is a client component. In a React Server Component, render it as-is or use `createAvatar(...).toDataUri()` in an ``. 8. For email and social images, convert to PNG on the server; many clients don't render SVG. 9. For brand colors, pass a list instead of one color: `colors: { clothing: ['#254CE9', '#F76549'] }`, `background: [...]`. The seed picks one entry, so each person keeps theirs. Never pick colors with `Math.random()`. 10. To recolor something that isn't a role (bag, mouth, drool, accents), use `palette` with keys from `croods.palette` or `avatar.sourceColors()`. Never guess artwork hex values. `#FFFFFF` is both skin and eye whites, so change skin with `colors.skin`. 11. The artwork is CC BY 4.0. Credit it once per app or site, somewhere visible such as the footer, about or credits page: "Croods by illlustrations" linking to https://illlustrations.co. Individual avatars, emails and exported SVGs need no caption.