@usenavii/core

Framework-agnostic engine. Seed in, SVG string out. Pure TypeScript, zero runtime dependencies, ~8 KB gzipped target.

Install

npm i @usenavii/core
# or pnpm / yarn / bun

Functions

createAvatar(seed: string, options?: AvatarOptions): string
random(options?: AvatarOptions): { svg: string; seed: string }
selectAvatar(seed: string, options?: AvatarOptions): AvatarSpec
renderAvatar(spec:  AvatarSpec, options?: AvatarOptions): string
renderAvatarInner(spec: AvatarSpec, options?: AvatarOptions): string
renderGroup(seeds:  string[], options?: GroupOptions): string

createAvatar is the convenience composition of selectAvatar + renderAvatar. Use the split pair when you want to inspect or mutate the spec between picking and rendering.

renderAvatarInner emits the SVG body without an outer <svg> wrapper — useful when composing multiple avatars into one SVG document (this is how renderGroup works internally).

Random avatars

Navii.random() picks a fresh seed for you and renders the avatar. Returns both the SVG and the chosen seed so you can persist it — saving the seed to the user's profile makes future renders stable.

import { Navii } from '@usenavii/core';

const { svg, seed } = Navii.random({ size: 96 });
// persist the seed so the user's avatar is stable on next visit
await db.users.update(user.id, { naviiSeed: seed });

Use for "spin again" UX, lazy onboarding (assign an avatar before the user picks one), dev/demo seeding. Seed source: crypto.randomUUID() with a Math.random() fallback.

React: stabilize the seed across re-renders with useState:

const [{ seed }] = useState(() => Navii.random());
return <Navii seed={seed} />;

Calling Navii.random() directly inside a render function (without useState/useMemo) gives you a new avatar on every re-render.

Seed helpers

seed(fields: SeedFields, options?: SeedOptions): string
seedFromEmail(email: string): string
normalizeEmail(email: string): string

Navii.seed({ id, email, name, createdAt }) picks the most unique field available: idemailname + createdAtname. The email branch is hashed by default with seedFromEmail() so PII never reaches the wire.

import { seed, seedFromEmail } from '@usenavii/core';

const s = seed({ id: user.id, email: user.email, name: user.name });
// id wins if present; otherwise sha256-of-email; otherwise name.

const hashed = seedFromEmail('alice@example.com');
// → "973dfe46…b4e813b" — sha256 hex of normalized email.

Privacy — why hash emails? Raw emails in URLs leak into server access logs, Referer headers, browser history, CDN cache keys, and analytics pixels. seedFromEmail() applies sha256(email.trim().toLowerCase()), so the seed stays stable and two services hashing the same way get the same avatar for the same person.

Migrating off raw-email seeds. If existing avatars are keyed on the plaintext email and changing them would surprise users, pass { hashEmail: false } to keep the old behavior:

seed({ email: user.email }, { hashEmail: false }); // legacy — avoid in new code

AvatarOptions

OptionTypeDefaultDescription
sizenumber (px)96Output canvas size. SVG viewBox is fixed at 100×100; size scales it.
paletteIdstringseededForce a specific palette. Pass any palette id.
palettePalette objectRuntime/brand palette object (e.g. pulled from Figma variables). Wins over paletteId. No registration in PALETTES required.
packsreadonly string[]Enable themed packs (premium content). Pack ids resolve against the built-in registry; unknown ids are silently skipped. Their palettes + parts merge into the selection pool, so the same seed renders differently from the base pool. Empty/undefined → base pool only.
styleStyleHint'masc' | 'femme' | 'neutral'. Biases seeded picks toward a gender expression. Only takes effect when an enabled pack defines styleHints. Determinism preserved: same seed + same style = same output.
backgroundenum or { color: string }seededOverride scene fill. Enum form picks from 'none' | 'solid' | 'ring'; object form supplies an exact color (SDK-only — URL form accepts enum only).
moodenumseeded'neutral' | 'happy' | 'serious' | 'sleepy' | 'wink'. Overrides seed-derived eyes + mouth with a curated pair. Same seed + mood = byte-identical. Bypasses pack eye/mouth constraints by design.
titlestringAdds role="img" and aria-label.
animatedbooleanfalseEmits inline <style> with idle animations. Honors prefers-reduced-motion.
tileBgstringOpaque circular fill behind avatar. Any CSS color or 'auto' to use palette accent.

AvatarSpec

The resolved description of an individual avatar — what selectAvatar returns and what renderAvatar consumes.

interface AvatarSpec {
  seed:       string;
  palette:    Palette;
  body:       BodyShapeId;
  eyes:       EyeStyleId;
  mouth:      MouthStyleId;
  antenna:    AntennaStyleId;
  accessory:  AccessoryId;
  background: BackgroundId;
  topper:     TopperId;

  // Continuous tweaks
  hueShift:        number;  // degrees, signed
  bodyScale:       number;  // 0.92–1.08
  eyeGapShift:     number;  // px (viewBox units), signed
  mouthCurveScale: number;  // 0.85–1.15
  antennaTilt:     number;  // degrees, signed
}

All *Id types are string unions. Palette is an object: { id, bodyFrom, bodyTo, accent, ink, blush }.

Advanced: composition

The split selectAvatar + renderAvatar lets you override any part programmatically — not just the two the HTTP API exposes.

import { selectAvatar, renderAvatar } from '@usenavii/core';

const base = selectAvatar('alice');
const svg = renderAvatar({ ...base, body: 'tall', eyes: 'star' }, { size: 128 });

GroupOptions

Extends AvatarOptions with these additional fields:

OptionTypeDefaultDescription
sizenumber64Per-tile size.
overlapnumber0.3Tile overlap fraction (0–0.7).
maxnumberallCap tiles; remainder collapses into +N.
ringstring#ffffffBorder ring around each tile.
tileBgstring#ffffffSolid fill behind each avatar.
counterFillstring#E5E7EBBackground of the +N tile.
counterInkstring#374151Text color of the +N tile.

Other exports

  • createRng(seed) — the PRNG used internally. Returns { next(), range(min, max), pick(arr) }.
  • cyrb53(string) — fast 53-bit string hash. Used to seed the PRNG.
  • @usenavii/core/parts subpath — exports the part-id arrays (BODY_IDS, EYE_IDS, etc.) and PALETTES.