New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

invadrs

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

invadrs

Deterministic space-invaders-style pixel avatars from any string.

latest
Source
npmnpm
Version
1.3.1
Version published
Maintainers
1
Created
Source

invadrs

Deterministic space-invaders-style pixel avatars from any string. Zero dependencies. Same id in, same avatar out, forever.

🕹 Live demo & Storybook →

invadrs sample avatars — the invadr() and spawn() primitives, shown across every built-in palette

import { invadr, spawn, dataUri } from "invadrs";

invadr("matt");                       // hand-drawn creature, SVG string
spawn("matt");                        // procedural creature, SVG string
invadr("matt", { palette: "sunset", size: 32 });
dataUri(invadr("matt"));              // data:image/svg+xml,...

Primitives

  • invadr(id, options?) — one of 16 hand-drawn creatures.
  • spawn(id, options?) — a unique procedural symmetric creature.

Options

{ size?, palette?, padding?, background?, title?, resolution?, sprite?, color?, salt?, accent?, tint?, pixelRatio? } (resolution is spawn-only and sprite is invadr-only; when size is omitted no width/height is set, so host CSS controls the size.)

color picks a palette index for the body. salt shifts every avatar (empty means none). accent fills the eyes and mouth with a second color. tint adds a faint background in a palette color (a background wins).

pixelRatio with size puts every cell edge on a whole device pixel. At a size that does not divide the grid evenly (20px is 13 cells of about 1.5px), Safari draws each cell over every pixel it touches and fills in the eyes and gaps; snapped, it matches Chrome. Pass window.devicePixelRatio. The React components snap on their own, measuring the size as laid out, so one that mounts inside a scaling animation (a menu opening) still snaps to its final size.

A distinct avatar per person

Two ids can land on the same creature and color. For a known set of people (a team, a chat room), distinctAvatars hands each id its own pair:

import { distinctAvatars, invadr } from "invadrs";

const avatars = distinctAvatars(["ana", "ben", "cy"], { palette: "neon" });
invadr("ben", { palette: "neon", ...avatars.get("ben") });

Pass the same palette and salt you render with. Each id keeps its own pair unless an earlier id holds it, so appending a person never changes anyone before them. A 6-color palette gives 96 distinct pairs; past that, pairs repeat.

Each pick also carries fill, the exact color the avatar is drawn with (a var(--...) string under css-vars), so the rest of your UI can match it: a cursor, a border, a "Ben is here" dot.

const { fill } = avatars.get("ben")!;
cursor.style.borderColor = fill;

By default only the pair is unique, so two people can share a color on different creatures. When color alone has to tell people apart, pass unique: "both": the first min(16, palette size) people each get their own creature and their own color, and anyone past that falls back to distinct pairs.

const avatars = distinctAvatars(memberIds, { palette, unique: "both" });

Picks follow list order, so removing or reordering people can move the people after them. To keep assignments fixed, keep the list in a stable order (say, by join time) or store each person's pick when they join.

Palettes & theming

Built-ins, each its own colour mood: tokyoNight (default, balanced rainbow), neon (electric, dark-bg), sunset (warm), ocean (cool), forest (greens/earth), mono (grayscale). Pass a name, your own string[], a full { colors, background } object, or "css-vars" to emit var(--accent)… fills that follow your theme.

import { palettes } from "invadrs";
const brand = { colors: [...palettes.tokyoNight.colors, "#ff00aa"] };
invadr("matt", { palette: brand });

React

import { Invadr, Spawn, InvadrsProvider } from "invadrs/react";

<Invadr id="matt" palette="css-vars" />   {/* hand-drawn creature */}
<Spawn id="matt" />                         {/* procedural creature */}

{/* app-wide defaults; explicit props on a component win */}
<InvadrsProvider palette="tokyoNight" size={20}>…</InvadrsProvider>

The components measure their drawn size and the screen's pixel ratio after mount and snap the cells to device pixels, so a CSS-sized avatar stays sharp with its eyes and gaps in every browser. A server render is unsnapped until it hydrates.

Stability contract

These are frozen:

  • the hash (FNV-1a finished with murmur3's fmix32)
  • the salt format (salt + "\u0000" + id; an empty salt means none)
  • the seed bit ranges (creature % 16, color >>> 4, accent >>> 12, tint >>> 20)
  • the accent rule (picked among the colors other than the body color; drawn in enclosed holes, found by a 4-way flood fill from the grid edge)
  • the tint opacity (0.18)
  • the built-in creatures (their art and order)
  • the built-in palette colors and their order
  • the procedural generator
  • the css-vars color order
  • the distinctAvatars assignment order

Changing any of them alters existing avatars and is only ever done in a major release.

Upgrading from 0.x

1.0 changes the hash, so every avatar changes. Replace distinctSprites by spreading the whole pick: const avatars = distinctAvatars(ids), then invadr(id, { ...avatars.get(id) }). Pass the same palette and salt you render with. The pick's .sprite alone is not equivalent, because only the (creature, color) pair is distinct. Pin 0.3.x to keep the old avatars.

Keywords

avatar

FAQs

Package last updated on 14 Oct 2026

Related posts