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,
});
// .package(url: "https://github.com/skyaara/holey-shapes.git", from: "2.1.0")
import HoleyShapes
HoleyShapeView(configuration: .seeded(user.id))
.frame(width: 40, height: 40)
// repositories { maven("https://jitpack.io") }
// implementation("com.github.skyaara:holey-shapes:v2.1.0")
HoleyShapeView(
config = HoleyShapeConfig.seeded(user.id),
modifier = Modifier.size(40.dp),
contentDescription = null,
)
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· ComposeHoleyShape.Discholesnumberper shapeHow many holes to cut, from 0 to 8. Clamped to what the shape can hold. Every hole has the same area.
seednumber0Picks the hole layout. Any integer from −1,000,000 to 1,000,000. The same seed always gives the same layout.
color#RRGGBBshape colorFace color.
Swift
faceColor: HoleyColor(hex:)· ComposefaceColor: ColorshadowColor#RRGGBBface darkened in OKLCHColor of the solid extrusion behind the face.
turn0 | 90 | 180 | 2700Quarter turns of the face. Other values snap to the nearest quarter turn. The extrusion keeps its direction.
Swift
turn· ComposeturnmirrorbooleanfalseFlips the face left to right. The extrusion keeps its direction.
Swift
mirrored· ComposemirrorshadowXshadowYnumber18, 21Extrusion offset in a 420-unit view box, from −80 to 80.
shadowStepsnumber12Slices in the extrusion, from 1 to 32. More slices look smoother.
animatedbooleantrueHoles close and reopen on hover, or on tap in the native SDKs. Respects reduced-motion settings, and the animation scale on Android.
durationnumber900Length of the hole animation in milliseconds, from 100 to 10,000.
Swift
durationin seconds (0.9) · ComposedurationMillisdoodlebooleanfalseWebHand-drawn ink outline with its own shadow. The shadow options apply to the solid style only.
idPrefixstringnoneWebUnique prefix for mask and filter IDs. Use one per SVG string when you put several on one page.
mountHoleyShapehandles this for you.contentDescriptionString?shape and holesComposeAccessibility label. Pass
nullfor decorative avatars.
API
createHoleySvg(options)→ stringReturns 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)→ mountWebRenders into an element or selector. The mount has
update(options),shuffle()anddestroy().seededHoleyOptions(id, animated?)→ { shape, holes, seed, turn, mirror, color, shadowColor, animated }A stable shape and seed for any string.
Swift
HoleyShapeConfiguration.seeded(id)· KotlinHoleyShapeConfig.seeded(id)identityHash(id)→ stringThe 16-digit hex hash that seeding starts from. Handy for caching or for checking that two platforms agree.
Swift
HoleyShapeConfiguration.identityHash(id)· KotlinHoleyShapeConfig.identityHash(id)shuffled()→ configurationThe same configuration with the next seed.
Web
mount.shuffle()shapeNames{ key, name, color }[]The shape catalog, for building pickers.
Swift
HoleyShape.allCases· KotlinHoleyShape.entries