seedicon turns a string into an SVG avatar. 18 styles, five options, no dependencies and no I/O, so the whole thing runs during server rendering and the same input always produces the same markup.
Install
npm i seedicon
Zero runtime dependencies. React is an optional peer dependency and only needed if you import from seedicon/react. Node 18+, ESM and CJS, types included.
Quick start
Two functions and one optional component. Pick whichever fits where you are rendering.
import { generateAvatar } from "seedicon";
// Raw <svg> markup as a string
const svg = generateAvatar({ seed: user.id, style: "glass", size: 64 });
// A data URI, for <img src> or CSS background-image
import { generateAvatarDataUri } from "seedicon";
const src = generateAvatarDataUri({ seed: user.id, style: "glass" });
// Or the React component
import { Avatar } from "seedicon/react";
<Avatar seed={user.id} style="glass" size={40} shape="circle" />;
The seed is whatever already identifies the user: a UUID, a database id, a wallet address, an email. Nothing is stored, fetched or cached. Calling the function twice gives you the identical string both times, so it is cheap enough to call at render time.
Options
Option
Type
Default
What it does
seed
string
required
The only input that decides what the avatar looks like. An empty string throws.
style
SeediconStyle
"pixels"
One of the 18 below. Only exists on the package root: the single-style entry points are the style.
size
number
64
Width and height in SVG user units, written to both the attributes and the viewBox.
shape
"square" | "rounded" | "circle"
"square"
Corner preset, scaled from size.
radius
number
from shape
Exact corner radius in the same units as size. Overrides shape.
Those five are the entire API surface. There is no theme, no palette override and no color option: the colors come out of the seed, which is what makes two people looking at the same user see the same avatar without agreeing on anything first.
Shape and radius
Avatars get clipped differently in every product, so the corner is an option rather than a decision baked into the markup. Three presets cover almost everything:
A preset is a fraction of size, not a fixed number of pixels, so the same shape holds at 16px and at 256px. That is the reason to prefer it over a hard-coded radius when the same avatar appears at several sizes in one product.
Setting the radius directly
When none of the three is the exact rounding your design asks for, pass radius. It is measured in the same units as size and takes precedence over shape whenever both are given, since shape is the coarse choice a design makes once and radius the fine-tuning a specific screen asks for.
generateAvatar({ seed: user.id, size: 64, radius: 14 });
// radius wins when both are passed — this is a 12px corner, not a circle
generateAvatar({ seed: user.id, size: 64, shape: "circle", radius: 12 });
The value is clamped to [0, size / 2]. Half the size is already a circle, so anything larger would only ever draw the same circle. A non-finite value throws rather than emitting broken markup.
Keeping one avatar consistent across sizes
If you set radius by hand and render the same avatar at several sizes, scale it with the size or the small one will look like a differently shaped icon rather than a smaller version of the large one. Or just use shape, which does this for you.
const ratio = 14 / 64; // the corner you designed, as a fraction
for (const size of [96, 56, 40, 24, 16]) {
generateAvatar({ seed: user.id, size, radius: size * ratio });
}
The corner is applied as a clip path over the finished artwork, which means it works identically for every style, including the ported ones. Styles do not opt in or out of it.
The 18 styles
Every avatar below is generated at build time by the package itself, on the server, from the same five seeds. Three styles are output-compatible with an existing library, meaning the same seed produces the exact same image and the test suite checks that against the original package on every run. The rest are seedicon implementations that borrow an idea.
pixels
An 8x8 grid mirrored down the middle, filled from a background, a foreground and a spot color. It is the wallet avatar you already know: blockies is what MetaMask and Etherscan render, and this is a port of that algorithm rather than a lookalike.
The port is exact, down to the order the random draws happen in and the loose int32 arithmetic in the original PRNG. The test suite renders both libraries for the same seeds and compares cell by cell and color by color, so an app already showing blockies can switch without a single user getting a new picture.
One thing to watch: an Ethereum address exists lowercase and checksummed, and those are different strings, so they produce different avatars. blockies behaves the same way. Keep passing whatever your app passes today, or normalize once at the edge with address.toLowerCase().
Good for: Wallet UIs, and any app migrating off blockies.
A 5x5 grid where every row is one of seven horizontally symmetric bit patterns. Because each row is a palindrome, no seed can produce something that reads as a rendering error, and with 7^5 = 16,807 arrangements the shapes stay recognizable at 16px instead of dissolving into noise.
The geometry vocabulary comes from DiceBear's Identicon style; the colors are seedicon's own, derived from the seed like every other style here. It is the same idea with our color model, not a compatible reproduction.
Good for: Dense lists and tables, where avatars are small and many.
A SHA-1 digest of the seed selects from 18 shape definitions, placed on a 4x4 grid with four-fold rotational symmetry and painted from a five-color theme. A faithful port: the digest, which hex character picks which shape, the theme, the lightness correction table and the geometry are all reproduced exactly.
The single addition is an opaque background. Jdenticon defaults to transparent, which is fine for an inline icon but not for a profile picture, and the corner clip needs a surface to cut. No shape changes.
Good for: Apps already rendering Jdenticon on another platform.
The 7x7 mirrored grid Stellar wallets use, the one that looks like a space invader. There is no PRNG in the original: the picture is the key, base32-decoded and read bit by bit, so you can tell at a glance that an address is the one you expected.
seedicon keeps that property. A real Stellar account key (56 base32 characters starting with G) is decoded exactly as the original does and produces the same image. Any other seed falls back to a SHA-1 digest, since an arbitrary string has no key bytes to read.
Good for: Stellar addresses, and anywhere the avatar is a verification aid.
The algorithm ssh-keygen prints next to a key fingerprint. A bishop walks a bounded board, taking one diagonal step per pair of bits read from the digest, and every square counts how many times it was stepped on. Walls neither wrap nor bounce: a move that would leave the board loses the component that would have gone off it, so the bishop slides along the edge.
Two departures from the original. The board is square, 11 by 11, because 17 by 9 letterboxes badly in an avatar slot and is unreadable small. And the bits come from SHA-1 rather than MD5, since this package already ships SHA-1 for two other styles and its 20 bytes give 80 moves.
Where ssh-keygen maps the visit counts to characters, this maps them to lightness. Squares crossed once are barely there and the ones the bishop kept returning to glow, which is what turns a walk into a picture.
Good for: SSH-adjacent tools, and anywhere the avatar should feel like a fingerprint.
A 12x12 Life automaton seeded from the hash, run for 12 generations on a wrapping grid, then mirrored. Cells are colored by how long they survived rather than by their final state, which is what produces the soft organic gradients instead of hard-edged noise.
Blockchain Commons' LifeHash runs the same idea on a SHA-256 digest at high resolution, compiling the whole generation history into an image. That would mean shipping SHA-256 and emitting a thousand rectangles per avatar, so this is a lighter reimplementation of the mechanism, not a port. Same look, 144 cells worst case.
Good for: Organic texture at medium and large sizes.
Every cell computes a value along a gradient, then compares it against a threshold from a Bayer 4x4 matrix tiled across the grid. Because the matrix spreads its sixteen thresholds as far apart on the grid as they are close in value, the cells that switch on interlock into the familiar checker texture, and two colors read as a continuous ramp.
The gradient is normalized against its own range over the canvas corners, so the ramp always spans the full distance whichever direction the seed picked. Without that step an off-axis gradient leaves most of the tile on one side of every threshold and the avatar comes out nearly blank.
Both tones are shades of one hue on purpose. Two unrelated hues would read as a checkerboard of two things rather than as one color fading into another.
Good for: Interfaces with a retro or terminal feel, at any size.
The one style here that is not an algorithm. Every other renderer computes its picture; this one assembles it from 139 sprites somebody drew — 45 hairstyles, 23 tops, 23 mouths, 14 pairs of glasses, 12 pairs of eyes, 10 hats, 8 beards and 4 accessories — layered over a fixed body in a fixed order. Glasses, hats, beards and accessories are accents that show up rarely, which is why most faces are plain and the occasional one is not.
The artwork is DiceBear's Pixel Art, dedicated to the public domain under CC0 1.0, and copied here unchanged. The randomization is seedicon's own, so the same seed does not produce the same face that @dicebear/pixel-art would.
It is also the only figurative style in the package. The rest are abstract marks — you cannot tell whose avatar it is, only that it is theirs. This one is a face, with a skin tone and gender-coded features, which means a user ID ends up assigning somebody an appearance. Some products want exactly that. Others deliberately do not, and that is worth deciding rather than defaulting into.
Good for: Consumer products and games, where a character beats an abstract mark.
A grid where every cell draws two quarter-circle arcs in one of two orientations. Both orientations put an arc endpoint at the midpoint of all four cell edges, so whatever the neighbours chose, the curves meet.
That is the whole design: the seed contributes exactly one bit per cell, and the result is always one or more continuous loops wandering the tile, never a loose end. It is the same trick that makes Truchet tiles work on a bathroom floor.
Good for: Anywhere you want pattern rather than a figure, at 32px and up.
Real heraldry works from a closed vocabulary: a shield is described by naming a division and a charge, not by drawing coordinates. This picks one of ten divisions (per pale, per fess, per bend, quarterly, per saltire, barry and the rest) and lays one of nine charges over it — roundel, annulet, lozenge, mullet, cross, chevron, pile, billet, crescent.
It is by a wide margin the smallest output in the package: two or three elements, around three hundred bytes, and it stays readable at 16px because everything on it is a large flat area.
The cost of that is entropy. Ten divisions and nine charges give ninety silhouettes, so across a large user base the shape repeats and the colors do the separating. Prefer it where avatars are seen a few at a time rather than in a directory of ten thousand.
Good for: Small avatars, tight markup budgets, and anything with a crest-like feel.
A handful of shapes drawn once inside a wedge, then repeated around the center five to eight times and mirrored. Symmetry does the work a narrow parameter range does elsewhere: scattered shapes are the classic way a generative avatar goes wrong, but the same scatter folded around a center reads as a mandala no matter where the shapes landed.
The wedge is emitted once into defs and referenced with use. At eight-fold symmetry with a mirror, writing the markup out sixteen times would have quadrupled the size of the output.
Good for: Decorative avatars at medium and large sizes.
Particles released on a grid and stepped through a vector field, each leaving a polyline behind. The field is a sum of two sines rather than a noise function, which is the whole trick: Perlin noise would mean shipping a permutation table, while sin(fx) + cos(fy) costs four numbers and produces the same swirling, hair-like structure.
Because every particle in a region reads the same field, neighbouring lines stay roughly parallel and the result always looks combed rather than tangled. There is no seed that produces a scribble.
It is the most expensive style in the package in markup terms — 36 lines of a dozen segments each. If you render many avatars per page and the HTML size matters, gradient and heraldry cost a tenth of it.
Good for: Large avatars and profile headers, where the detail has room.
Two or three line gratings laid over each other. The visible pattern is not the lines, it is the beat between them, and the beat's wavelength goes as roughly gap over sin of the angle difference.
That relationship is the entire design constraint. At a large angle difference the beat is finer than the lines themselves and the tile flattens into gray at any avatar size, which is exactly what a first cut at this style did. So the extra gratings are pinned to within 6 to 26 degrees of the first, where the beat comes out several times coarser than the grating and survives being looked at from across a screen.
Gaps are fractions of the size rather than fixed units, so the pattern is the same picture at 24px and at 256px.
Good for: Op-art and print-inspired interfaces, at 40px and up.
Five ridges front to back over a flat sky, with an optional sun placed behind them. Back layers sit high and shallow, front layers low and jagged, which is the whole of the depth cue. Every layer is a shade of one hue.
The composition is completely fixed, so no seed can produce something that reads as broken. The tradeoff is worth knowing before picking it: because the structure never varies, two seeds differ mostly in color and peak placement. It reads as scenery more than as an identity mark.
Good for: Calm, atmospheric products; large avatars and cover images.
Blobs built from randomized-radius points around a circle, smoothed with quadratic curves and blurred, layered over a flat background color.
The silhouette is generated rather than picked from a fixed set of drawings, so two seeds never share an outline. It is the softest style here and the one that reads best at large sizes, where the blur has room to show.
Good for: Profile headers and cards, where the avatar is large.
Four to six bands stacked bottom-up in shades of one hue, with lightness moving in a single direction. The seed picks the hue and the direction, never the colors independently, so no seed can produce a clashing pair.
Wave amplitude is capped below half the spacing between bands. Each band paints over everything below it, so if two crests could cross, the upper band would swallow the lower one and the avatar would collapse into two flat areas. The cap makes that impossible, which is why every band always stays visible.
DiceBear's Waves ships as paths exported from a design tool, so this is not a port: the bands here are generated, and every seed gets its own silhouette.
Good for: Calm, low-contrast avatars that do not fight the UI.
Two or three color stops on a linear gradient at a seed-derived angle. No grid, no shapes: closest to a design tool's random gradient button, except reproducible.
It produces the smallest markup of any generated style, a couple of elements regardless of size, and it stays legible at 12px since there is no detail to lose.
Good for: Tiny avatars, and anywhere the markup budget is tight.
Two white shapes at 60% opacity, blurred so far past their outlines that only a soft glow is left, over a saturated background. The seed picks the background hue, which two of eight shapes are drawn, and where each one sits and how far it is rotated.
The artwork is DiceBear's Glass, dedicated to the public domain under CC0 1.0. The eight shapes are the letters of the word "gradient", which is the joke of the original: blurred this hard, a letter stops being a letter and is only a differently shaped brush. The randomization is seedicon's own, so the same seed does not produce the same picture that @dicebear/glass would.
The background does not come from the palette every other style shares. The glow needs a saturated, mid-lightness ground to lift out of, and on the near-white or near-black colors that palette can produce it either washes out or disappears. The original also blends the layers with mix-blend-mode: screen, which does nothing to a white source, so it is dropped here: the pixels are identical, and the avatar renders the same in SVG rasterizers that never implemented blend modes.
Good for: Colorful avatars where a flat color is too plain and a pattern too busy.
Most products pick a style once and never change it. In that case, do not import the package root: it resolves styles by name through a lookup table, so it has to reference all 18 renderers and no bundler can drop the ones you never call.
Every style has its own entry point. Importing one pulls in that renderer plus the shared core, which is the seeded hash, the SVG wrapper and the corner clip. Nothing else ships.
The single-style functions take the same options as generateAvatar minus style, which the entry point already is.
Import path
Exports
seedicon/pixels
pixels, pixelsDataUri
seedicon/identicon
identicon, identiconDataUri
seedicon/jdenticon
jdenticon, jdenticonDataUri
seedicon/stellar
stellar, stellarDataUri
seedicon/randomart
randomart, randomartDataUri
seedicon/lifehash
lifehash, lifehashDataUri
seedicon/dither
dither, ditherDataUri
seedicon/pixelart
pixelart, pixelartDataUri
seedicon/truchet
truchet, truchetDataUri
seedicon/heraldry
heraldry, heraldryDataUri
seedicon/kaleidoscope
kaleidoscope, kaleidoscopeDataUri
seedicon/streamlines
streamlines, streamlinesDataUri
seedicon/moire
moire, moireDataUri
seedicon/terrain
terrain, terrainDataUri
seedicon/marble
marble, marbleDataUri
seedicon/waves
waves, wavesDataUri
seedicon/gradient
gradient, gradientDataUri
seedicon/glass
glass, glassDataUri
One caveat: <Avatar> takes a style name, so it goes through the root and pulls in all 18. If the bundle matters more than the convenience, call the single-style function and render the markup yourself. It is four lines.
It takes the same options as generateAvatar plus className, which is passed to the wrapping element, and defaults size to 40. The markup is memoized on the options, so re-renders that do not change the avatar do not regenerate it.
Unlike canvas-based generators, this renders synchronously to SVG: no effect, no DOM ref, no flash of an empty avatar on first paint, and it works during server rendering in Next.js, Remix or anything else, because there is no canvas and no window anywhere in the package. Every avatar on this page was rendered on the server, which is the proof.
import { writeFileSync } from "node:fs";
import { gradient } from "seedicon/gradient";
writeFileSync("avatar.svg", gradient({ seed: user.id, size: 512 }));
In a database
There is nothing to persist: keep calling generateAvatar with the id you already store. If you want the style to be changeable per user without re-deriving it, store the style name, which is a short string, rather than the rendered markup.
// on signup
user.avatarStyle = "pixels";
// on render
<Avatar seed={user.id} style={user.avatarStyle} size={40} shape="rounded" />;
The pixels style is the default, so no style is needed and no avatar changes. Watch the address casing: lowercase and checksummed forms are different strings and blockies did not normalize them either, so keep passing exactly what you pass today.
Determinism
No randomness, no I/O, no clock. The same { seed, style, size, shape, radius } produces byte-for-byte identical markup, in the browser, in Node, on an edge runtime, today and next year. That is what lets you render an avatar on the server and again on the client without a hydration mismatch, and cache the output anywhere.
Anything that changes those five values changes the image, including size: geometry is computed in output units, not scaled after the fact. Casing matters too, on purpose, because usernames and emails are seeds as well and Maria should be free to differ from maria. Normalize at the edge if you need otherwise.
The hash is a doubled 32-bit FNV-1a, not a cryptographic one. It only has to spread seeds out visually, so do not use it for anything security-sensitive. Two of the ported styles do run SHA-1, because their originals do and compatibility requires it.
Source, tests and issues live on GitHub. 18 styles, MIT.