Custom Algorithm

One of the core ideas behind @maxnth/gestalt is that avatars are just deterministic drawing functions. You can write your own algorithm and plug it into the generic <Avatar> component.

The AvatarAlgorithm interface

import type { AlgorithmRenderContext, AvatarAlgorithm } from '@maxnth/gestalt'

interface AlgorithmRenderContext {
  ctx: CanvasLikeContext
  size: number
  seed: AvatarSeed
}

interface AvatarAlgorithm {
  name: string
  render: (context: AlgorithmRenderContext, options?: unknown) => void
}
  • name — a unique identifier for debugging and Storybook controls.
  • render — the drawing function. It receives a CanvasLikeContext, the internal render size in pixels, and the raw seed.

Seed handling

Use toSeed(seed) to normalize any string or number into a stable numeric value.

import { toSeed } from '@maxnth/gestalt'

const numericSeed = toSeed('[email protected]')

Example: polka-dot algorithm

import type { AlgorithmRenderContext, AvatarAlgorithm } from '@maxnth/gestalt'
import { seededRandom, toSeed } from '@maxnth/gestalt'

const polkaAlgorithm: AvatarAlgorithm = {
  name: 'polka',
  render({ ctx, size, seed }: AlgorithmRenderContext) {
    const random = seededRandom(toSeed(seed))
    const dots = 12

    ctx.fillStyle = '#f8fafc'
    ctx.fillRect(0, 0, size, size)

    ctx.fillStyle = '#6366f1'
    for (let i = 0; i < dots; i++) {
      const x = random() * size
      const y = random() * size
      const r = random() * size * 0.12 + size * 0.04
      ctx.beginPath()
      ctx.arc(x, y, r, 0, Math.PI * 2)
      ctx.fill()
    }
  },
}

Note: seededRandom is exported from @maxnth/gestalt so your custom algorithms share the same deterministic PRNG as the built-in ones.

Use it like any other algorithm:

<script setup>
import { Avatar } from '@maxnth/gestalt'
import { polkaAlgorithm } from './polka-algorithm'
</script>

<template>
  <Avatar seed="user-42" :size="96" :algorithm="polkaAlgorithm" />
</template>

Tips

  • Keep render deterministic. The same seed must always paint the same output.
  • Do not read from external state like Date.now() or Math.random().
  • Use ctx.fillRect, ctx.fillStyle, gradients, and paths freely — anything supported by a normal 2D context.
  • If you need advanced text metrics, cast ctx to CanvasRenderingContext2D inside your algorithm function.