# Nurblings Avatars: full documentation
# Getting started
# Getting started
Nurblings turns any string into a small, curious creature. The same string
always hatches the same Nurbling, so you can give every user, team or
repository an avatar without storing anything.
## Install
```sh
npm install @nurblings/react # Next.js, React
npm install @nurblings/vue # Nuxt, Vue
npm install @nurblings/astro # Astro
npm install @nurblings/element # any HTML, Svelte, Angular
npm install nurblings # plain JavaScript
```
Using a coding agent? `npx skills add albertesparragoza/nurblings` teaches
Claude Code, Cursor, Codex and others how to use the library.
## What each package supports
Every package draws the same avatar from the same seed. They differ in where
the drawing happens and how you pass options.
| Feature | React | Vue | Astro | HTML element | JavaScript |
| --- | --- | --- | --- | --- | --- |
| Server-side rendering | Yes | Yes | Yes | No | Yes |
| Client JavaScript for a still avatar | None | Your Vue app | None | Element and core | Optional |
| Built-in themes | Prop | Prop | Prop | Attribute | `themed()` |
| Your own colours | Prop | Prop | Prop | Property | `createNurblings()` |
| Light, dark and auto mode | Yes | Yes | Yes | Yes | Yes |
| Motion | Yes | Yes | Yes | Yes | Yes |
| Motion speed | Yes | Yes | Yes | No | Yes |
| Stops for reduced motion | Yes | Yes | Yes | Yes | Yes |
| Glides between views | Yes | Yes | Yes | Yes | Yes |
| App-wide configuration | Prop or provider | Prop or plugin | Prop | Property | `createNurblings()` |
| Pinned traits | Yes | Yes | Yes | Yes | Yes |
- **Server-side rendering:** React renders as a Server Component, Vue renders
on the server and hydrates with your app (Nuxt included), and Astro ships
static SVG. The HTML element draws in the browser.
- **Themes:** `theme="lagoon"` on React, Vue, Astro and the element;
`themed('lagoon').nurbling(seed)` in plain JavaScript.
- **Your own colours:** `theme={palette([...])}` (Vue: `:theme`), or
`el.nurblings = themed(palette([...]))` on the element. `palette()` and
`themed()` come from `nurblings/themes`.
- **App-wide configuration:** a `nurblings` prop everywhere, plus
`NurblingsProvider` in React client components and `NurblingsPlugin` in Vue.
- **Glides between views:** `morph()` from `nurblings/transition` runs in the
browser: call it from client code in React and from a `
```
Server rendering works out of the box in Nuxt.
## Astro
```astro
---
import { Nurbling } from '@nurblings/astro'
---
```
The component renders static SVG and adds no client JavaScript.
## Any other framework: a custom element
`@nurblings/element` works in plain HTML, Svelte, Solid, Angular, Lit or a
CMS template:
```ts
import '@nurblings/element/define'
```
```html
```
Attributes mirror the options; see the
[package README](https://github.com/albertesparragoza/nurblings/tree/develop/packages/element).
## Alive by default
Every Nurbling breathes, blinks and sways its antennae, with CSS only. Pass
`animate: false` for a still avatar; [Motion and transitions](/docs/motion.md) has
the rest.
## Plain HTML, or an image URL
Use a data URI anywhere an image goes:
```ts
import { nurbling, nurblingSrc } from 'nurblings'
img.src = nurblingSrc('ada@example.com', { size: 64 })
```
## Next steps
- Tune the motion, or glide avatars between places: [Motion and transitions](/docs/motion.md)
- Your palette, designs and parts: [Customising](/docs/customising.md)
- Every option, and how pinning works: [API reference](/docs/api.md)
- Why an avatar never changes: [Generations and stability](/docs/api.md#generations-and-stability)
---
# Motion and transitions
# Motion and transitions
Every Nurbling breathes, blinks and sways its antennae. The motion is CSS
inside the SVG: no script and no ids, so it works in server components, in an
`
` and in a `data:` URI. Each seed gets its own timing, so a grid of
avatars never moves in unison.
## Turn motion off, or pick layers
```ts
nurbling(seed) // every layer
nurbling(seed, { animate: false }) // a still drawing
nurbling(seed, { animate: { blink: false } }) // everything but blinking
nurbling(seed, { animate: { hover: false, speed: 0.5 } }) // calmer, no hover
```
Framework components take the same value: `animate={false}` in React and
Astro, `:animate="false"` in Vue. On ``, `animate="false"`
draws a still avatar and a list keeps only some layers:
`animate="blink hover"`.
| Layer | What moves |
| --- | --- |
| `breath` | The body squashes and stretches slowly from its base. |
| `blink` | The eyes blink every few seconds. |
| `antennae` | Each antenna drifts on its own clock. |
| `hover` | The antennae wiggle faster while the pointer is over the avatar. |
`speed` multiplies every clock (2 is twice as fast) and must be a positive
number. Sleepy Nurblings move a little slower.
## When nothing moves
- When the viewer asks for reduced motion. Every rule sits inside
`prefers-reduced-motion: no-preference`, so a paused avatar is simply its
still drawing.
- At 32 px and below, where motion reads as noise.
For long lists, `animate: { breath: false, antennae: false }` keeps just the
blink and the hover.
Each animated avatar carries its own small style block, under 1 KB before
compression and the same text in every avatar, because styles cannot be
shared between SVGs without ids. For hundreds of avatars on one page,
`animate: false` is the lighter choice.
## Move an avatar between places: `morph(update, options?)`
A Nurbling can glide from a dialog into a list, or from a hero into a header,
instead of jumping. Give the avatar the same `transition` key in both places,
then make the change inside `morph`:
```ts
import { morph } from 'nurblings/transition'
// the same key in the list and in the dialog links the two
nurbling(user.id, { size: 48, transition: user.id })
nurbling(user.id, { size: 320, transition: user.id })
await morph(() => dialog.close())
```
`update` must change the page before it returns, or return a promise that
resolves once it has:
```tsx
// React
morph(() => flushSync(() => setOpen(false)))
```
```ts
// Vue
morph(async () => {
open.value = false
await nextTick()
})
```
`duration` (milliseconds, default 450) and `easing` (any CSS easing) tune the
move. Under reduced motion the change simply happens.
## How it works
- Browsers with View Transitions use them when each key is on screen once.
- Otherwise, and in every other browser, the avatar is animated from its old
box to its new one with a transform, which keeps the vector sharp. A new
avatar flies out of a twin that stays on screen, so a dialog opening over a
list animates too.
- A name the page set itself on a tagged element is restored afterwards, and
overlapping calls never undo each other.
- The helper is its own entry, under 1 KB: pages that never import it pay
nothing.
## Across page navigations
For moves between pages, use your framework's view transitions and set
`view-transition-name` to `transitionName(key)`, also exported by
`nurblings/transition`, so the names match the ones `morph` uses.
---
# Customising
# Customising
`createNurblings(config)` makes an instance with your palette, body designs,
drawn parts and defaults. Configure it once and use it everywhere:
```ts
import { createNurblings, SILHOUETTES } from 'nurblings'
export const avatars = createNurblings({
// brand colours replace the built-in palette
shells: { mist: '#e4ebf2', sand: '#f1e4cf' },
accents: { ink: '#2c5fd9', coral: '#c94f38', forest: '#2e7d4f' },
// designs are added to the built-in ones; false drops one
silhouettes: {
pear: false,
robot: { hw: 1.1, width: 0.9, belly: 0.3, tip: 0.9, rows: 4, cols: 2, plates: 'side', weight: 2 },
},
slots: {
mouth: false, // drop a part
extra: () => '', // replace it
brow: (ctx, base) => `${base()}`, // wrap it
},
defaults: { size: 48, background: 'circle' },
})
avatars.nurbling('user-42')
avatars.traits('user-42', { silhouette: 'robot' }) // custom names are typed
```
## The rules
- **Colours are paired by contrast.** An accent is used only if it reads on
light and dark pages (2.5:1) and on a shell (3:1); every shell needs
readable eyes and at least two such accents. Setup throws a `RangeError`
naming the colour when that is not possible, so a palette that would give
unreadable faces fails before a single avatar renders.
- **Slots and parts** change or add drawn parts: see
[Extending parts](#extending-parts). Their markup goes into the SVG as it is,
so build it from your own code, the anchors and the colours, never from user
input, and put any text from `props` through `ctx.esc`. Keep it free of `id`
attributes, so many avatars can share a page. Treat what `ctx` holds as
read-only, and return a string.
- **Custom body designs** need finite numbers, a positive `hw` and `width`, a
known `plates` zone, and at most 12 plate rows and 12 columns. Setup throws a
`RangeError` naming the design and the field otherwise.
- The same config and seed always render the same avatar. No seed draws
Nurbi: he is `nurbi()` from `nurblings/nurbi`.
## Extending parts
Every drawn part is a named layer, painted in this order: `backdrop`,
`antennae`, `body`, `plates`, `extra`, `eyes`, `brow`, `mouth`, and any part
a config adds. Three tools work on them, and they combine:
- **`slots`** change a part, built in or added: return markup to replace it,
call `base()` to wrap it, or pass `false` to drop it.
- **`parts`** add a new part, painted right after the part it names
(`after`, `mouth` by default). Added parts step aside at 32 px and below
unless they set `small: true`.
- **`use`** stacks presets. A preset is an ordinary config, so a recipe, a
brand kit and a plugin are all the same thing.
### What every part reads
Slots and parts all receive the same `ctx`. Built-in parts are drawn from the
same anchors and colours, so a part placed on them fits every body, follows
every theme and switches with the page.
| Field | Is |
| --- | --- |
| `anchors` | Named places, in drawing units: `centre`, `base`, `crown`, `eyes` (each with `x`, `y`, `rx`, `ry`), `brow`, `mouth`, `chest`, `box`, and `band(f)`, the body's `left` and `right` edges at a height from `0` (base) to `1` (crown) |
| `colours` | Each colour role as drawn for this avatar: `shell`, `eye`, `accent` (brow), `mark` (antennae), `wear` (extras), `ground` (container). A theme sets them all |
| `paint(role, 'fill' \| 'stroke')` | A role as an attribute. In `mode: 'auto'`, `ground` and `mark` switch with the page, as the built-in parts do |
| `random(name)` | A random stream of its own, the same for an avatar every time |
| `theme`, `options`, `props` | The theme in use, the call's options, and data from the config and the call (the call wins) |
| `small`, `size`, `mode` | 32 px and below, the pixel size, and the page mode |
| `n(value)`, `esc(text)` | A number as the renderer writes it, and text made safe for SVG |
| `traits`, `geometry` | The raw traits and body shape, for anything else |
### Recipes
Each recipe is a preset. The home page draws every one of them.
**Body.** Wrap it and add a belly patch in a lighter shade of its own shell:
```ts
import { createNurblings } from 'nurblings'
import { setFill, shade } from 'nurblings/themes'
const belly = {
slots: {
body: ({ anchors: a, colours, n }, base) => {
const { left, right, y } = a.band(0.2)
return `${base()}`
},
},
}
```
**Eyes.** Keep them and put round glasses over them:
```ts
const glasses = {
slots: {
eyes: ({ anchors: { eyes: [l, r] }, n }, base) => {
const size = l.rx * 1.7
return `${base()}
`
},
},
}
```
**Mouth.** Replace it with a smile in the eye colour, as wide as the eyes are
apart. The built-in mouth steps aside at 32 px; this one does too:
```ts
const smile = {
slots: {
mouth: ({ anchors: a, small, paint, n }) => {
if (small) return ''
const w = (a.eyes[1].x - a.eyes[0].x) * 0.35
return ``
},
},
}
```
**Extras.** Swap the scarf, collar or hat for a heart pin on the chest:
```ts
const pin = {
slots: {
extra: ({ anchors: a, small, n }) => {
if (small) return ''
const x = a.chest.x + (a.band(0.2).right - a.chest.x) * 0.25
const y = a.band(0.2).y
return ``
},
},
}
```
**A new part.** A status dot that reads `props` on each call, ringed in the
container colour so it switches with the page:
```ts
const status = {
parts: {
status: {
after: 'mouth',
small: true,
draw: ({ anchors: a, props, paint, n }) => {
const { right, y } = a.band(0.14)
const fill = props.status === 'online' ? '#2e9e5b' : '#9a9a9a'
return ``
},
},
},
}
```
**Backdrop.** Keep the container and give it your brand colour:
```ts
const brand = {
slots: { backdrop: (ctx, base) => setFill(base(), '#ffd166') },
}
```
### Combining
Put presets in `use`, or combine them with `compose()`. They apply in order:
- Slots chain: each later slot's `base()` is what the earlier ones drew, so
two presets can both wrap the eyes, and a preset can restyle a part another
one added.
- A later slot that does not call `base()` replaces everything before it; a
later `false` drops the part, and a slot after that starts from nothing.
- Parts, body designs, colours and props merge by name, later names winning.
The last theme wins, and defaults merge.
```ts
export const avatars = createNurblings({
use: [belly, glasses, smile, pin, status, brand],
theme: lagoon,
})
avatars.nurbling(user.id, { props: { status: user.online ? 'online' : 'away' } })
```
A slot for a name that is no part, or a part placed after one that does not
exist, throws a `RangeError` at setup. Antennae and brows can be slotted too,
but they carry the family look, so wrap them rather than replace them.
### Collections: new eyes, mouths and extras
The seed picks each avatar's eye shape, mouth and extra from a list. `eyes`,
`mouths` and `extras` add to those lists, the same way `silhouettes` adds body
designs, so a season's accessories are one preset:
- A new name joins the list and draws itself with `draw(ctx)`. `weight` sets
how often it comes up: 1 by default, where each built-in extra weighs 1 and
`none` 6.
- `false` drops a built-in choice. Drop them all to wear only your own.
- A built-in name with `draw` gets a new look; with `weight`, a new share.
- New mouths and extras step aside at 32 px and below; eyes always show.
- Only that trait changes. Every other trait of a seed stays as it was.
```ts
const winter = {
extras: {
beanie: {
draw: ({ anchors: a, colours, n }) => {
const rows = [0.62, 0.68, 0.74, 0.8, 0.85].map((f) => a.band(f))
const right = rows.map((r) => `${n(r.right)},${n(r.y)}`)
const left = rows.map((r) => `${n(r.left)},${n(r.y)}`).reverse()
return ``
},
},
bowtie: {
weight: 2,
draw: ({ anchors: { chest: c }, colours, n }) =>
``,
},
},
}
// add them to the extras
createNurblings({ use: [winter] })
// or wear only the new season
createNurblings({ use: [winter], extras: { none: false, scarf: false, hat: false, collar: false } })
avatars.nurbling(user.id, { extra: 'beanie' }) // new names can be pinned, and are typed
```
Body designs follow the same rule: `silhouettes: { robot: {...}, pear: false }`
adds one and drops another.
## Use it everywhere
```tsx
// React server components: pass it as a prop (wrap once in your own component)
// React client components: a provider
import { NurblingsProvider } from '@nurblings/react/client'
{children}
```
```ts
// Vue and Nuxt: install once
import { NurblingsPlugin } from '@nurblings/vue'
app.use(NurblingsPlugin(avatars))
```
```ts
// Astro:
// Custom element: el.nurblings = avatars
```
## Themes
Ten themes ship in `nurblings/themes`. Pass one by name to any component, or
hand it to `createNurblings`:
```tsx
```
```ts
import { createNurblings } from 'nurblings'
import { lagoon } from 'nurblings/themes'
export const avatars = createNurblings({ theme: lagoon })
```
| Theme | Feel |
| --- | --- |
| `lagoon`, `punch`, `candy`, `picnic`, `sorbet`, `terracotta` | Soft bodies on a strong container colour |
| `marble`, `riso`, `lime`, `bauhaus` | Vivid bodies with one dark ink for every mark |
Importing a theme by name keeps only that one in your bundle. The `theme="..."`
prop on the components looks names up in `THEMES`, which adds about 1 KB.
### Your own colours
`palette()` turns 2 to 5 colours into a theme, in any order. The lightest
become bodies, the darkest the brows and antennae, and the rest the containers.
```ts
import { palette } from 'nurblings/themes'
const brand = palette(['#264653', '#e9c46a', '#f4a261'])
createNurblings({ theme: brand })
```
| Colours | Bodies | Brows and antennae | Containers |
| --- | --- | --- | --- |
| 2 | 1 | 1 | a tint of the body |
| 3 | 1 | 1 | 1 |
| 4 | 2 | 1 | 1 |
| 5 | 2 | 2 | 1 |
Two colours give a two-tone family. Themes are repaired, never refused: brows
and antennae are darkened or lightened until they read on every body, and eyes
turn light on a dark body.
For full control, write the theme object yourself:
```ts
const night = {
name: 'night',
shells: { moon: '#e8e4f7', dusk: '#b9b3e6' },
accents: { ink: '#2b2350' },
backdrops: ['#2b2350', '#6a5acd'], // containers on light pages
darkBackdrops: ['#15122b'], // containers on dark pages
} satisfies Theme
```
## Light and dark pages
`mode` tells an avatar what page it sits on. The creature never changes; its
container colour does, and so do the antennae where they would disappear into it.
```tsx
```
`auto` needs no JavaScript. The nearest `data-theme="dark"` or `.dark` ancestor
turns it dark, the nearest `data-theme="light"` or `.light` one keeps it light,
at any depth, and with neither the OS setting decides.
## Colour helpers for slots
Most slot code is no longer needed once a theme exists. When a slot still
recolours, `nurblings/themes` has the pieces:
```ts
import { contrast, ensureContrast, pickBy, readableOn, recolour, setFill, shade } from 'nurblings/themes'
createNurblings({
theme: brand,
slots: {
backdrop: (ctx, base) => setFill(base(), pickBy(['#264653', '#2a9d8f'], ctx.traits.silhouette.grain)),
brow: (ctx, base) => recolour(base(), ctx.traits.palette.accent, '#1b1b1b'),
},
})
```
| Helper | Does |
| --- | --- |
| `contrast(a, b)` | The WCAG contrast ratio of two colours |
| `ensureContrast(fg, bg, min)` | `fg`, moved until it reads on `bg` at `min`:1 |
| `readableOn(ground, candidates, min?)` | The first candidate that reads on `ground` |
| `shade(hex, k)` | Toward white for `k > 0`, toward black for `k < 0` |
| `pickBy(list, n)` | One item per whole number, such as the seed's grain |
| `recolour(markup, from, to)` | Swaps one colour in a slot's markup |
| `setFill(markup, fill)` | Sets the first fill, such as the container's |
---
# API reference
# API reference
Everything below is exported from the `nurblings` package. It has no runtime
dependencies and runs anywhere JavaScript does: browsers, Node, Deno, Bun,
edge runtimes and build-time rendering.
```ts
import { nurbling, traits, toDataUri } from 'nurblings'
```
## `nurbling(seed, options?)`
Returns one Nurbling as an SVG string.
```ts
nurbling('user-42')
nurbling('user-42', { size: 48, background: 'circle', title: 'Ada Lovelace' })
```
Seeds are normalised first (see [`normaliseSeed`](#normaliseseedseed)), so
`Ada Lovelace`, `ada.lovelace` and `ADA_LOVELACE` hatch the same Nurbling.
Prefer a stable user id over an email: ids never change, and normalisation
means two different strings can hatch the same creature.
### Options
| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `size` | `number` | `128` | Width and height in pixels, a positive number. At 32 and below, small-size mode drops the mouth and extras so the face still reads, and motion stops. |
| `background` | `'none' \| 'circle' \| 'squircle' \| 'square'` | `'none'` | The container behind the Nurbling, in a tint of its own shell colour. |
| `title` | `string` | `'Nurbling'` | The accessible name, used for `aria-label` and ``. Escaped for you. |
| `decorative` | `boolean` | `false` | Hides the avatar from assistive technology (`aria-hidden`, no name), for an avatar beside the person's name in text. |
| `animate` | `boolean \| Motion` | `true` | Ambient life: breath, blink, antenna drift and a hover wiggle. `false` draws a still avatar; an object picks layers. See [Motion and transitions](/docs/motion.md). |
| `frame` | `'auto' \| 'full' \| 'portrait'` | `'auto'` | How the Nurbling sits in its square. `portrait` crops closer on the face, which reads better in small round avatars; `full` shows the whole figure. `auto` uses a portrait at 48 px and below. |
| `mode` | `'light' \| 'dark' \| 'auto'` | `'light'` | The page the avatar sits on. The container colour and, where needed, the antennae change; the creature does not. `auto` follows the OS or a `data-theme="dark"` or `.dark` ancestor with CSS only. |
| `transition` | `string` | none | A key for `morph()`: the same key in two places links them, so the avatar glides between them. Adds a `data-nurbling-transition` attribute. |
| `gen` | `1` | `1` | The trait generation. Pin it to keep an avatar identical across future releases. |
| `mood` | `'neutral' \| 'curious' \| 'pleased' \| 'thinking' \| 'sleepy'` | from the seed | Pins the resting mood. |
| `mouth` | `'none' \| 'line' \| 'smile'` | from the seed | Pins the mouth. |
| `extra` | `'none' \| 'scarf' \| 'hat' \| 'collar'` | from the seed | Pins the extra. |
| `silhouette` | `'classic' \| 'basketball' \| 'squat' \| 'firm' \| 'round' \| 'bell' \| 'wide' \| 'smooth' \| 'pear' \| 'tall'` | from the seed | Pins the body design. Each seed still varies it slightly and gets its own plate pattern. |
| `shell` | `'mint' \| 'sky' \| 'butter' \| 'peach' \| 'lilac' \| 'sage' \| 'cloud' \| 'blush' \| 'lemon'` | from the seed | Pins the body colour. The accent is still chosen from the colours that read well on it. |
Pinning one trait never changes any other: every trait is drawn from its own
random stream. The one exception is `shell`: pinning it re-picks the accent
from the colours that read well on that shell, so the brow and antennae keep
their contrast.
An unknown value for `gen`, `mood`, `mouth`, `extra`, `silhouette` or `shell`
throws a `RangeError`, and so do a `size` or an `animate.speed` that is not a
positive number.
## `traits(seed, options?)`
Returns the resolved traits a seed hatches, without rendering: silhouette,
antennae, eyes, brow, mouth, extra, mood and palette. Useful for tests, for
theming around an avatar (the palette's `shell` and `accent` are good UI
colours), or for rendering with `renderTraits`. It returns a fresh object every
time: changing it never affects any other avatar.
## `nurblingSrc(seed, options?)`
The avatar as a data URI in one call, for an `
`, a CSS `url()` or an
email. Takes the same options as `nurbling()`. A `createNurblings` instance has
the same thing as `.src(seed, options?)`.
```ts
img.src = nurblingSrc(user.id, { size: 64, background: 'circle' })
```
## `toDataUri(svg)`
Wraps an SVG string as a `data:` URI for an `
` or a CSS
`background-image`.
```ts
const src = toDataUri(nurbling('user-42', { size: 64 }))
```
## `renderTraits(traits, options?)`
Renders a traits object you built or edited yourself. Takes the rendering
options: `size`, `background`, `title`, `decorative`, `animate`, `frame`,
`mode` and `transition`. Every value in the traits is checked first: a
colour that is not 6-digit hex, a name outside the known traits or a number
that is not finite throws a `RangeError`, so traits from a database or a
form can never inject markup.
## `normaliseSeed(seed)`
The normalisation every seed goes through before hashing: Unicode NFKC, lower
case, accents and other combining marks stripped, whitespace and punctuation
removed. Emoji are kept, so an emoji seed still hatches its own Nurbling.
## `nurbi(options?)`
From `nurblings/nurbi`: Nurbi, the family's flagship character, as the stored
drawing. It takes the rendering options: `size`, `background`, `title`,
`decorative`, `animate`, `frame` and `transition`. He has an entry of his own
so no other import carries his drawing. The seed `nurbi` is an ordinary
Nurbling.
## `createNurblings(config)`
Makes an instance with your palette, body designs, drawn parts and defaults,
returning `{ nurbling, traits, src }` with the same signatures as above. Its
calls also take `theme` and `props`. Colours are checked for contrast, and
part positions and slot names are checked, at setup. See
[Customising](/docs/customising.md).
| Config | What it does |
| --- | --- |
| `theme` | A colour set for every avatar |
| `shells`, `accents` | Body and mark colours, replacing the built-in ones |
| `silhouettes`, `eyes`, `mouths`, `extras` | Lists the seed picks from. New names are added to the built-in ones, `false` drops one, and `weight` sets how often each comes up. New eyes, mouths and extras draw themselves with `draw(ctx)` |
| `slots` | Replace, wrap or drop any part by name, built in or added |
| `parts` | New parts, each `{ after?, small?, draw(ctx) }` |
| `props` | Data every slot and part reads as `ctx.props`; a call's `props` win |
| `use` | Presets applied first, in order: each is a config like this one |
| `defaults` | Options for every call |
Every slot and part receives the same context: `anchors`, `colours`,
`paint()`, `random()`, `theme`, `options`, `props`, `n()`, `esc()`, `small`,
`size`, `mode`, `traits` and `geometry`. See
[Extending parts](/docs/customising.md#extending-parts).
## `compose(...configs)`
Combines configs into one, the same way `use` does: slots chain, each later
one wrapping what the earlier ones drew; parts, designs, colours and props
merge by name, later names winning; the last theme wins; defaults merge.
```ts
const avatars = createNurblings(compose(brand, glasses, status))
```
## `nurblings/themes`
Named themes, `palette()` and colour helpers, in their own entry. Components
take `theme="lagoon"`; `createNurblings({ theme })` takes a theme object. See
[Customising](/docs/customising.md#themes).
| Export | Does |
| --- | --- |
| `lagoon`, `punch`, `candy`, `picnic`, `sorbet`, `terracotta`, `marble`, `riso`, `lime`, `bauhaus` | The built-in themes |
| `THEMES`, `ThemeName` | All of them by name |
| `palette(colours, name?)` | A theme from 2 to 5 hex colours |
| `themed(theme)` | A ready instance: `themed('lagoon').nurbling(seed)` |
| `contrast`, `ensureContrast`, `readableOn`, `shade`, `pickBy`, `recolour`, `setFill` | Colour helpers for slots |
| `themeOf(theme)` | A theme from its name, or the theme object itself; an unknown name throws a `RangeError` |
| `renderNurbling(renderer, seed, options, theme?)` | What the framework components call: an app-wide instance when there is one, a named theme when given, otherwise the default family. For writing a component of your own |
## `morph(update, options?)` and `transitionName(key)`
From `nurblings/transition`, a separate entry of under 1 KB. `morph` applies a
page change and moves every avatar tagged with a `transition` key from where it
was to where it lands. `options` is `MorphOptions`: `duration` in milliseconds
(450 by default) and `easing`, any CSS easing. `transitionName(key)` returns
the `view-transition-name` a key uses, for cross-page transitions in your
framework. See [Motion and transitions](/docs/motion.md).
## Constants and types
| Export | What it is |
| --- | --- |
| `SILHOUETTES`, `SHELLS`, `ACCENTS` | Generation 1's body designs, body colours and accent colours by name: the values the matching options accept |
| `SLOTS`, `SlotName` | The drawn parts in paint order, the names `slots` and `parts.after` take |
| `NurblingOptions`, `RenderOptions` | The options of `nurbling()` and of `renderTraits()` |
| `Traits` and its parts (`Silhouette`, `Antennae`, `Eyes`, `Brow`, `Palette`, …) | What `traits()` returns and `renderTraits()` takes |
| `NurblingsConfig`, `ConfiguredOptions`, `Nurblings` | A `createNurblings` config, its call options, and the instance it returns |
| `Slot`, `Slots`, `Part`, `SlotContext`, `CallOptions`, `Variant`, `Variants`, `Anchors`, `Point`, `EyeAnchor`, `ColourRole`, `Props`, `SilhouetteShape` | The pieces of an extension; see [Customising](/docs/customising.md) |
| `NurblingRenderer` | Anything with a `nurbling(seed, options)` method: the default package or an instance. Every component's `nurblings` prop takes one |
| `Rng` | The seeded random source behind `ctx.random()` |
| `Theme`, `Hex`, `Motion`, `Mode`, `Frame`, `Background`, `Generation` | The value types the options use |
## Generations and stability
Traits ship in numbered generations. Within a generation, a seed always hatches
the same Nurbling: releases never change existing avatars. The exact SVG for a
seed is stable for the same options and configuration; motion markup is a
layer on top of the drawing and can be turned off with `animate: false`. New
shapes, colours or extras arrive as a new generation, and you opt in by
passing `gen`.
## Accessibility
- Every SVG has `role="img"`, an `aria-label` and a ``. Pass `title`
with the person's name, for example `title: 'Ada Lovelace'`.
- When the name is already written next to the avatar, pass
`decorative: true` instead, so screen readers do not read it twice.
- Colours are chosen for contrast: the accent clears 3:1 on the body, and the
antennae read on both light and dark pages.
- Motion is on by default, stops completely under `prefers-reduced-motion`,
and turns off with `animate: false`.
## Licence of generated avatars
The code is MIT. Every avatar it generates is dedicated to the public domain
under CC0: use them anywhere, commercially or not, without attribution.
Nurbi's exact body design and colours are not among the generator's choices.
The one exception is Nurbi himself, the stored drawing that
`nurbi()` from `nurblings/nurbi` returns: the names Nurbi and Nurblings and
that artwork are reserved, see [TRADEMARKS.md](https://github.com/albertesparragoza/nurblings/blob/develop/TRADEMARKS.md).