Docs

Deterministic avatars for the web, SwiftUI and Jetpack Compose. Same input, same avatar, on every platform.

Get started

// npm i holey-shapes
import { createHoleySvg, seededHoleyOptions } from 'holey-shapes';

avatar.innerHTML = createHoleySvg({
  ...seededHoleyOptions(user.id),
  doodle: true,
  idPrefix: user.id,
});

Web runs in browsers and Node 18+ with no dependencies. SwiftUI needs iOS 16, macOS 13, tvOS 16 or visionOS 1. Compose needs Android API 26.

Avatars from IDs

seededHoleyOptions(id) turns any string, like a user ID, email or username, into a complete holey: shape, hole count, quarter turn, mirroring, color and layout seed. The same string always gives the same avatar, and Swift and Kotlin give exactly the same result as JavaScript.

Under the hood it hashes the ID with identityHash(id), a 64-bit FNV-1a hash returned as 16 hex digits. You don't have to use it. The avatar depends only on shape and seed, so any hash of your own works:

import { createHoleySvg, shapeNames } from 'holey-shapes';

const hash = Math.abs(myHash(user.id));

avatar.innerHTML = createHoleySvg({
  shape: shapeNames[hash % shapeNames.length].key,
  seed: hash % 1_000_001,
});

Seeded holeys combine 14 shapes, 3 hole counts, 4 turns, 2 mirrors and 3,600 OKLCH hues with a layout seed.

Options

Every option is optional. Out-of-range values are clamped, and invalid ones fall back to the default.

shapeShapeKey'disc'

Which outline to draw. One of the 14 shapes.

Swift .disc · Compose HoleyShape.Disc

holesnumberper shape

How many holes to cut, from 0 to 8. Clamped to what the shape can hold. Every hole has the same area.

seednumber0

Picks the hole layout. Any integer from −1,000,000 to 1,000,000. The same seed always gives the same layout.

color#RRGGBBshape color

Face color.

Swift faceColor: HoleyColor(hex:) · Compose faceColor: Color

shadowColor#RRGGBBface darkened in OKLCH

Color of the solid extrusion behind the face.

turn0 | 90 | 180 | 2700

Quarter turns of the face. Other values snap to the nearest quarter turn. The extrusion keeps its direction.

Swift turn · Compose turn

mirrorbooleanfalse

Flips the face left to right. The extrusion keeps its direction.

Swift mirrored · Compose mirror

shadowX shadowYnumber18, 21

Extrusion offset in a 420-unit view box, from −80 to 80.

shadowStepsnumber12

Slices in the extrusion, from 1 to 32. More slices look smoother.

animatedbooleantrue

Holes close and reopen on hover, or on tap in the native SDKs. Respects reduced-motion settings, and the animation scale on Android.

durationnumber900

Length of the hole animation in milliseconds, from 100 to 10,000.

Swift duration in seconds (0.9) · Compose durationMillis

doodlebooleanfalseWeb

Hand-drawn ink outline with its own shadow. The shadow options apply to the solid style only.

idPrefixstringnoneWeb

Unique prefix for mask and filter IDs. Use one per SVG string when you put several on one page. mountHoleyShape handles this for you.

contentDescriptionString?shape and holesCompose

Accessibility label. Pass null for decorative avatars.

API

createHoleySvg(options)→ string

Returns a self-contained SVG string. Works the same in browsers and Node, so you can render on the server.

SwiftUI and Compose HoleyShapeView(…)

mountHoleyShape(target, options)→ mountWeb

Renders into an element or selector. The mount has update(options), shuffle() and destroy().

seededHoleyOptions(id, animated?)→ { shape, holes, seed, turn, mirror, color, shadowColor, animated }

A stable shape and seed for any string.

Swift HoleyShapeConfiguration.seeded(id) · Kotlin HoleyShapeConfig.seeded(id)

identityHash(id)→ string

The 16-digit hex hash that seeding starts from. Handy for caching or for checking that two platforms agree.

Swift HoleyShapeConfiguration.identityHash(id) · Kotlin HoleyShapeConfig.identityHash(id)

shuffled()→ configuration

The same configuration with the next seed.

Web mount.shuffle()

shapeNames{ key, name, color }[]

The shape catalog, for building pickers.

Swift HoleyShape.allCases · Kotlin HoleyShape.entries

Share links

Every handle has its own page at /@handle, like holey-shapes.aakashreddy.com/@ada. Handles are case-insensitive and the @ is optional. Post the link on X and the preview card shows that holey. The card image itself lives at /og/@handle.png.

Shapes