cjscrofani.com
back to work
·4 min read

Geometricon — Deterministic geometric icons

TypeScriptSVGDesignCLIOpen Source

Generate unique, symmetrical geometric icons from hash strings — a strict grid, a small glyph vocabulary, and a hash-derived 3-color palette, in pure dependency-free TypeScript.

Geometricon turns any hash string — or any raw string, hashed internally — into a minimal, deterministic geometric icon. Every icon is built from a strict grid, a small vocabulary of quarter-disc glyphs, and a 3-color palette derived from the hash — so a whole set of icons reads as one designed system. Same hash, same icon, every time.

View on GitHub

The problem

Deterministic avatars are a solved problem if you don't care how they look. Hash a user id, light up some pixels, and you have the classic identicon — unique, reproducible, and indistinguishable from static. Put twenty of them in a comment thread and the page reads as noise, because that's what they are: randomness made visible.

What I wanted was the opposite: default marks that look like someone drew them on purpose. A set that hangs together like a family, pairs with both light and dark UIs, and still needs no database — the string itself is the storage.

From scatter to system

The first version generated icons the way most libraries do: pick several shapes — circles, squares, triangles, hexagons, diamonds — and scatter them by hash across a composition, with translucent fills and optional gradients. Each icon was fine in isolation. A set of them was confetti: overlapping translucency muddied the colors, and nothing tied one icon to the next.

The redesign inverted the philosophy. Instead of getting variation from randomness across loose constraints, get it from arrangement within strict ones. Everything snaps to a grid. The glyph vocabulary is tiny — quarter-discs, half-discs, triangles, circles. Every icon gets exactly three solid colors derived from the hash: a tinted tile, a deep ink of the same hue, and an accent at a harmonious hue shift. No opacity, no gradients.

Even the compositions are curated rather than random: quad arranges quarter-discs into one of four designed motifs (bloom, pinwheel, focus, wave) and lets the hash pick a single "twist" cell; orbit is one disc plus one accent element; mosaic is the classic mirrored identicon grid rebuilt with the disc vocabulary. The hash decides which arrangement you get, never whether it's composed. The original scatter generator survives as style: 'classic'.

Quad

2×2 quarter-disc motifs with one hash-picked twist cell. The default.

quad style sample 1, light variantquad style sample 2, light variantquad style sample 3, light variant

Orbit

One disc, one accent element. The most minimal; best for decorative marks.

orbit style sample 1, light variantorbit style sample 2, light variantorbit style sample 3, light variant

Mosaic

Mirrored 3×3 grid — the most distinctive per hash, best for avatars.

mosaic style sample 1, light variantmosaic style sample 2, light variantmosaic style sample 3, light variant

Samples generated by the tool itself; they follow this site's light/dark theme.

Details that mattered

Determinism is a contract, not a feature. A golden-fixture test suite pins the exact SVG output for a set of inputs. Any change that alters a byte of output fails the suite — because "same hash, same icon" means every existing user's icons change the moment you break it, and that should be a deliberate decision, not a side effect.

Antialiasing seams. Abutting glyphs of the same color render with hairline gaps where the rasterizer blends edges. Every shape strokes itself with its own fill color — a half-pixel outline you never see, except in the seams it removes.

Icons share pages. SVG clipPath ids are namespaced by hash, style, tile, and variant — an early version collided when the same hash was rendered twice with different options, and one icon silently clipped with the other's mask.

Sets can match a brand. A hue lock (hue / hueRange) constrains the derived palette to a site's color family, and the accent is allowed to step outside the range on purpose — locked base plus contrasting accent beats three shades of the same blue.

Using it

Pure TypeScript, zero runtime dependencies, shipped as a dual ESM/CJS build with a CLI. Output is a plain SVG string — inline it, write it to a file, or serve it.

# Straight from text — hashed internally (FNV-1a)
geometricon --text "[email protected]" -o avatar.svg

# Style, variant, tile
geometricon abc123 --style mosaic --variant dark --tile circle -o m.svg

# Lock the hue so a whole set matches your palette
geometricon abc123 --hue-range 200,260 -o blue-family.svg
import { generateIcon, generateIconFromText } from 'geometricon';

const svg = generateIconFromText('[email protected]');

const svg2 = generateIcon(hash, {
  style: 'orbit',   // 'quad' | 'orbit' | 'mosaic' | 'classic'
  variant: 'dark',  // 'light' | 'dark'
  tile: 'circle',   // 'rounded' | 'circle' | 'none'
});

const svg3 = generateIconFromText('alice', { hueRange: [200, 260] });

Status

MIT-licensed and in use for the sample icons on this page. An npm release is next; for now it runs from a checkout. If the three styles aren't enough, the grid and glyph vocabulary are designed to take a fourth without breaking the family resemblance — that's the real test of the system.