Deterministic avatars from any string — for React Native + Expo. Wallet address, username, UUID → the same avatar every time. Two modes: gradient (diffuse blurred blends) and dither (Bayer halftone). Skia‑rendered, crisp at any size.
expovatar is a React Native port of hashvatar: the same FNV‑1a → mulberry32 seeding, OKLCH palette, and generation logic, with the canvas renderers rebuilt on @shopify/react-native-skia.
import { Expovatar } from 'expovatar'
<Expovatar hash="favourafula.bnb" size={48} />
<Expovatar hash="chidinma" size={64} mode="dither" />
<Expovatar hash="0x742d…f44e" size={64} tones={['hotpink', '#00ff99']} animated />Skia is a native module — expovatar runs on an Expo dev client / prebuild / EAS build, not Expo Go.
npm install expovatar
npx expo install @shopify/react-native-skia@shopify/react-native-skia (≥1.5) is the only peer. No Reanimated / Gesture Handler needed — animation uses a plain requestAnimationFrame loop.
import { Expovatar } from 'expovatar'
function Row() {
return (
<>
<Expovatar hash="favourafula.bnb" size={64} />
<Expovatar hash="adaeze" size={40} mode="dither" />
<Expovatar hash="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" size={40} tones={['#6366f1']} />
</>
)
}The component renders a circle by default (rounded); pass rounded={false} for a square.
| Prop | Type | Default | Description |
|---|---|---|---|
hash |
string |
— | Any string. Same string → same avatar. |
size |
number |
64 |
Square size in px. |
mode |
'gradient' | 'dither' |
'gradient' |
Render style. |
animated |
boolean |
false |
Animate (drifting blends / swirling dither). |
dotScale |
number |
auto | Dither cell size; scales with size if omitted. |
tones |
string[] |
— | Restrict the palette — hex, oklch(l c h), or common CSS names. |
rounded |
boolean |
true |
Clip to a circle. |
style |
ViewStyle |
— | Style on the wrapping view. |
Gradient — six irregular polygons, each blurred (MaskFilter) and composited with a blend mode (SrcOver / Overlay / SoftLight) over a base color, for a soft diffuse blob. Monotone from the hash by default.
Dither — an 8×8 Bayer‑matrix halftone: a directional gradient thresholded into crisp cells of one color over another. Great for a retro / pixelated identity.
<Expovatar hash="emeka.bnb" mode="gradient" />
<Expovatar hash="emeka.bnb" mode="dither" dotScale={3} />Restrict the palette to a hue family. Without tones, the avatar is monotone from the hash.
<Expovatar hash="ngozi" tones={['hotpink']} /> // one family
<Expovatar hash="ngozi" tones={['#ff6b6b', '#4ecdc4']} /> // multiple
<Expovatar hash="ngozi" tones={['oklch(0.65 0.25 310)']} /> // oklchAccepts hex (#ff69b4 / ff69b4), oklch(l c h), and common CSS color names (red, hotpink, teal, …). Unknown names fall back to the monotone palette; hex and oklch() always work.
Everything deterministic is exported for custom rendering or theming — none of it touches Skia:
import { hashToColors, hashToSeeds, oklchToHex } from 'expovatar'
const colors = hashToColors('favourafula.bnb', undefined, 4) // OKLCH[]
const hexes = colors.map(oklchToHex) // ['#…', …]
const seeds = hashToSeeds('favourafula.bnb', 8) // 8 numbers in [0,1)hashToColors(hash, tones?, count?)→ the generated OKLCH palette.hashToSeeds(hash, count)→countdeterministic numbers in[0, 1).oklchToHex/oklchToCss/rgbToOklch/parseTone→ color helpers.drawGradient/drawDither→ low‑level Skia painters (draw into anySkCanvas/PictureRecorder) if you want to composite avatars yourself.
hash → FNV‑1a → mulberry32 seeds → an OKLCH palette → a Skia picture. The picture is recorded once and memoized; animated re‑records on a requestAnimationFrame loop. Same string always yields the same seeds, palette, and picture.
The hash / color / generation logic ports near‑verbatim from hashvatar. The web renderers (<canvas> 2D, ctx.filter, globalCompositeOperation, ImageData) become Skia: MaskFilter blur, BlendMode, and crisp drawRect cells.
npm install
npm test # vitest — pure-core determinism
npm run typecheck
npm run build # tsup → dist (ESM + CJS + d.ts)MIT. Ported from hashvatar (MIT, © Médhy Chabour) — see LICENSE.