npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@m-kgl/dnd-pixel-sprites

v0.4.0

Published

20 animated pixel-art chibi sprites of the 10 classic D&D 5e classes (male + female), with an engine-agnostic TypeScript runtime and a React component. Zero runtime dependencies.

Readme

@m-kgl/dnd-pixel-sprites

Animated pixel-art sprites for the 10 classic D&D 5e classes — with typed React components, a character picker, health display, runtime recoloring and zero runtime dependencies.

  • 64×64 chibi-style sprites, 6-frame idle animation, male + female variants (20 sprites)
  • React components that work out of the box — built-in theme, no classNames wiring needed
  • English API — UI texts default to English, optional German via language="de"
  • Engine-agnostic vanilla canvas API underneath
  • Yarn PnP / Vite / Webpack compatible (assets resolved via exports map + import.meta.url)

Classes

barbarian · bard · cleric · druid · fighter · monk · paladin · ranger · rogue · wizard

Installation

npm install @m-kgl/dnd-pixel-sprites
# or
yarn add @m-kgl/dnd-pixel-sprites
# or
pnpm add @m-kgl/dnd-pixel-sprites

React ≥ 18 is an optional peer dependency — the vanilla API works without it.

Quickstart — React

import { DndSprite, HealthBar, DndCharacterPicker } from "@m-kgl/dnd-pixel-sprites/react";
import "@m-kgl/dnd-pixel-sprites/react/theme.css"; // once, e.g. in main.tsx

// Animated sprite — styled and working immediately
<DndSprite characterClass="wizard" gender="female" scale={4} />

// Health bar
<HealthBar value={13} max={20} editable />

// Full character picker (carousel + gender toggle + colors)
<DndCharacterPicker showColorPicker onConfirm={({ characterClass, gender, colors }) => ...} />

Every component is individually usable: import only what you need, the rest is tree-shaken. All components ship with built-in theme classes (dnd-* namespace) — pass unstyled to opt out, or override per element via className / classNames.

Language (i18n)

The code API is always English (props, types, class ids, callbacks). Displayed UI texts default to English and can be switched to German per component:

<DndCharacterPicker language="de" />   // "Barbar", "Auswählen", "männlich", ...
<HealthBar language="de" />            // "LP", aria: "Lebenspunkte: 13 von 20"
<ClassAttributes characterClass="wizard" language="de" />  // "Trefferwürfel", German descriptions

Available: language="en" (default) | "de". Dictionaries are exported as UI_TEXTS / getTexts(language) if you need them.

React API (@m-kgl/dnd-pixel-sprites/react)

<DndSprite> — the core component

<DndSprite characterClass="wizard" gender="female" scale={4} />

| Prop | Type | Default | Description | |---|---|---|---| | characterClass | DndClass | — | Required. One of the 10 class ids | | gender | "male" \| "female" | "male" | Character gender | | colors | SpriteColors | — | Custom colors per region (palette swap), e.g. { hair: "#b8442a" } — see Color selection | | scale | number | 4 | Pixel scaling (1 = native 64×64, 4 = 256×256) | | flipX | boolean | false | Mirror horizontally | | alpha | number | 1 | Opacity 0..1 (ghost/stealth effects) | | paused | boolean | false | Freeze the animation | | animation | string | "idle" | Animation name (currently only "idle") | | hp | number | — | Current hit points → drives health effects (tint/animation). Without hp the sprite stays normal | | maxHp | number | 20 | Maximum hit points (for the state calculation) | | healthState | HealthState | — | Set the state explicitly (overrides the hp calculation) | | healthEffects | boolean | true | Health reaction (tint + animation) on/off | | healthFlash | boolean | true | Short damage/heal flash on hp change | | showHitbox | boolean | false | Debug: red hitbox overlay | | background | string \| null | null | Canvas background color (null = transparent) | | src | string \| URL \| HTMLImageElement | built-in asset | Custom sprite URL (overrides the default PNG) | | onLoad / onError | callbacks | — | Load lifecycle | | fallback | ReactElement | — | Rendered while loading | | hideSkeleton | boolean | false | Disable the loading skeleton | | smoothTransition | boolean | true | Fade-in on load/class change | | language | "en" \| "de" | "en" | Language for aria texts | | className, style, id, aria-label, role, mouse/pointer events | — | — | Pass-through to the <canvas> |

Forwards ref to the <canvas> (e.g. for your own getContext() work).

10 typed convenience components

import { Barbarian, Wizard, Ranger, Paladin } from "@m-kgl/dnd-pixel-sprites/react";

<Barbarian scale={4} />
<Wizard gender="female" paused />
<Ranger flipX scale={6} onClick={...} />
<Paladin showHitbox alpha={0.5} />

All exported: Barbarian, Bard, Cleric, Druid, Fighter, Monk, Paladin, Ranger, Rogue, Wizard. Props identical to <DndSprite> minus characterClass.

Sprite reacts to health

Give the sprite hp (and optionally maxHp, default 20) — image and idle animation adapt automatically across 5 states:

const [hp, setHp] = useState(20);

<DndSprite characterClass="fighter" hp={hp} maxHp={20} scale={4} />
<HealthBar value={hp} max={20} onChange={setHp} editable />

| State | HP (of 20) | Look | Animation | |---|---|---|---| | full | 15–20 | normal | normal idle | | healthy | 10–14 | normal | normal idle | | wounded | 5–9 | slightly desaturated + red shimmer | a bit slower | | critical | 3–4 | red tint, darker | slow + occasional wince | | dying | 0–2 | grayscale, faded, red vignette | very slow + slumping |

  • Damage/heal (hp decreases/increases) trigger a short flash + shake or heal bounce → disable with healthFlash={false}.
  • Disable everything with healthEffects={false}, or pin a state with healthState="dying".
  • The tint (filter/opacity) is applied inline — works even without theme.css. The motion animations (wince/slump/flash) come from theme.css and respect prefers-reduced-motion.

Color selection

Every character can be recolored via palette swap — pixel-perfect, shading preserved, outline and ground shadow untouched:

<DndSprite
  characterClass="wizard"
  gender="female"
  colors={{ hair: "#b8442a", skin: "#8d5524", outfit: "#2e7a3e" }}
/>

8 color slots (SpriteColors): skin, hair, outfit, outfitAccent, metal, weapon, weaponAccent, accent. Unset slots keep the class's original colors. A color change is just a canvas repaint — no PNG reload.

<ColorPicker> — ready-made picker UI (swatches per slot + free color + reset):

import { ColorPicker, DndSprite, type SpriteColors } from "@m-kgl/dnd-pixel-sprites/react";

const [colors, setColors] = useState<SpriteColors>({});

<DndSprite characterClass="fighter" colors={colors} />
<ColorPicker colors={colors} onChange={setColors} slots={["skin", "hair", "outfit"]} />

| Prop | Type | Default | Description | |---|---|---|---| | colors / defaultColors | SpriteColors | — | Controlled / uncontrolled | | onChange | (colors) => void | — | Provides the complete colors object | | slots | ColorSlot[] | skin, hair, outfit, outfitAccent | Which rows appear | | presets | Record<ColorSlot, string[]> | curated palettes | Custom swatches per slot | | labels | Record<ColorSlot, string> | localized | Custom labels | | allowCustomColor | boolean | true | <input type="color"> for free colors | | showReset | boolean | true | "Auto" per row + "Reset" for all | | language | "en" \| "de" | "en" | Label language | | unstyled | boolean | false | Drop built-in theme classes |

Inside <DndCharacterPicker> the color picker is integrated (showColorPicker); the chosen colors come back in onChange/onConfirm:

<DndCharacterPicker
  showColorPicker
  colorSlots={["skin", "hair", "outfit"]}
  onConfirm={({ characterClass, gender, colors }) => save(characterClass, gender, colors)}
/>

Vanilla (no React): sprite.setColors({ hair: "#b8442a" }) / sprite.getColors(), or directly createSprite("wizard", { colors: {...} }). Low level: recolorSprite(image, meta.colorSlots, colors).

Note: slots that aren't visible on a sprite change nothing — e.g. the fighter wears a helmet, so hair has no effect there. If two slots share the same original color (e.g. gold accents on the cleric), the last one set wins.

<HealthBar> — hit point display

<HealthBar value={13} max={20} onChange={setHp} editable />

5 states (auto, based on %): full ≥75% · healthy ≥50% · wounded ≥25% · critical >10% · dying ≤10% — exported as getHealthState(value, max).

3 variants:

<HealthBar variant="bar"    value={13} max={20} />  {/* default — modern gradient bar */}
<HealthBar variant="hearts" value={13} max={20} />  {/* pixel-art hearts */}
<HealthBar variant="pips"   value={13} max={20} />  {/* compact dots (HUD-friendly) */}

Built-in UX (all automatic): damage/heal flash, ghost fill (briefly shows lost HP), floating "-3"/"+5" popup, critical pulse, dying shake, rapid-click accumulation, role="meter" accessibility.

| Prop | Type | Default | Description | |---|---|---|---| | value / defaultValue | number | — / max | Controlled / uncontrolled | | max / min | number | 20 / 0 | Range | | onChange | (value) => void | — | Fired on +/- | | variant | "bar" \| "hearts" \| "pips" | "bar" | Display form | | editable | boolean | false | Show +/- controls | | showValue | boolean | true | Show "13 / 20" | | label | string \| false | localized "HP" | Header label | | animations | boolean | true | Flash/pulse/shake on/off | | size | "sm" \| "md" \| "lg" | "md" | Size | | language | "en" \| "de" | "en" | Label/aria language | | unstyled | boolean | false | Drop built-in theme classes | | renderSymbol | (props) => ReactNode | — | Custom heart/pip shape |

<DndCharacterPicker> — carousel + gender toggle + colors

<DndCharacterPicker
  onChange={({ characterClass, gender, colors }) => ...}  // every change
  onConfirm={(selection) => ...}                          // confirm button
  showConfirmButton
  showColorPicker
/>

| Prop | Type | Default | Description | |---|---|---|---| | initialClass / initialGender | — | first / "male" | Uncontrolled start | | characterClass / gender | — | — | Controlled mode | | onClassChange / onGenderChange / onColorsChange | callbacks | — | Granular listeners | | onChange | (SelectionResult) => void | — | Combined listener | | onConfirm | (SelectionResult) => void | — | With showConfirmButton | | classes | DndClass[] | all 10 | Subset/order | | showSlider | boolean | false | Range slider | | showDots | boolean | true | Dots indicator | | showNavButtons | boolean | true | ‹ › buttons | | showGenderButton | boolean | true | ♂/♀ toggle | | showConfirmButton | boolean | false | Confirm button | | showColorPicker | boolean | false | Integrated color picker | | colorSlots | ColorSlot[] | 4 defaults | Slots in the color picker | | colors / defaultColors | SpriteColors | — | Controlled / uncontrolled colors | | hideTitle / hidePosition / showKeyboardHint / swipeEnabled | boolean | — | UI toggles | | hideAttributes / onlyAttributeFields / hiddenAttributeFields / attributesOverride | — | — | Attributes section | | renderSprite / renderGenderButton | render props | — | Custom rendering | | language | "en" \| "de" | "en" | UI language | | unstyled | boolean | false | Drop built-in theme classes |

Keyboard: ← → switch class, G toggles gender. Touch: swipe left/right. SelectionResult = { characterClass, gender, colors? }.

<DndSpriteCard> — sprite + attributes (+ HealthBar)

<DndSpriteCard
  characterClass="wizard"
  gender="female"
  scale={3}
  onlyAttributeFields={["hitDie", "primaryAbility", "specialResource"]}
  attributesOverride={{ displayName: "Mira the Wise" }}
/>

With integrated health — hp feeds the sprite (tint/animation react) and healthBar renders the bar below; "editable" adds +/-:

const [hp, setHp] = useState(20);

<DndSpriteCard
  characterClass="fighter"
  hp={hp}
  maxHp={20}
  healthBar="editable"   // true = display only
  onHpChange={setHp}
  hideAttributes
/>

Additional props: colors, spriteProps, healthBarProps (e.g. { variant: "hearts" }), hideTitle, hideSubtitle, hideDescription, renderTitle, renderSubtitle, language, unstyled, classNames.

<ClassAttributes> — stats block (no sprite)

<ClassAttributes
  characterClass="wizard"
  onlyFields={["hitDie", "primaryAbility", "abilities"]}
  attributes={{ specialResource: { name: "Spell slots", max: 4, value: 2 } }}
/>

Fields: hitDie, primaryAbility, savingThrows, armor, weapons, spellcasting, specialResource, abilities, tags, description. Data access without rendering: getClassAttributes("wizard", "en" | "de") / DEFAULT_CLASS_ATTRIBUTES.

Hook: useDndSprite()

const { sprite, loaded, error } = useDndSprite({ characterClass: "wizard", gender: "female", colors });
// plus useSpriteAnimationLoop({ sprite, canvas, scale, speed, ... }) for your own canvas

Styling

All components ship with default classes in the dnd-* namespace, styled by theme.css:

import "@m-kgl/dnd-pixel-sprites/react/theme.css";

Three levels of customization:

  1. CSS variables (global theming):
    :root {
      --dnd-bg: #20202a;
      --dnd-accent: #4f7cff;
      --dnd-radius: 12px;
      --dnd-font: system-ui, sans-serif;
      /* HP colors: --dnd-hp-* */
    }
  2. className / classNames per component — your classes are appended to the defaults (extend/override specific rules).
  3. unstyled — drops all built-in classes; only your classNames apply (e.g. for Tailwind).

State hooks for your own CSS: data-state (HealthBar), data-health + data-hit (sprite), data-active (swatches/symbols), data-variant, data-size, data-slot.

Mobile-ready out of the box: 44px touch targets, swipe gestures, responsive breakpoints (≤640px, ≤360px), prefers-reduced-motion respected.

Vanilla / engine-agnostic API

import { createSprite } from "@m-kgl/dnd-pixel-sprites";

const wizard = createSprite("wizard", { gender: "female", colors: { hair: "#b8442a" } });
await wizard.load();

const ctx = canvas.getContext("2d")!;
let last = performance.now();
requestAnimationFrame(function tick(now) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  wizard.animate(ctx, 128, 230, now - last, { scale: 4 });
  last = now;
  requestAnimationFrame(tick);
});
  • createSprite(characterClass, { gender?, colors? }) / createAllSprites(gender?)
  • Class constructors: WizardSprite, FighterSprite, ... (new WizardSprite("female"))
  • DndSprite methods: load(src?), setAnimation(name), update(dt), draw(ctx, x, y, opts), animate(...), getHitbox(x, y, scale), setColors(colors), getColors(), isLoaded()
  • Recolor utilities: recolorSprite(image, colorSlots, colors), createColorMap(...), parseHexColor(hex)
  • Constants: DND_CLASSES, GENDERS, CLASS_DISPLAY_NAMES, CLASS_DISPLAY_NAMES_DE, SPRITE_URLS, SPRITE_META
  • Custom sprite source: wizard.load(myUrl) or asset imports:
    import wizardPng from "@m-kgl/dnd-pixel-sprites/sprites/wizard-female.png";
    import wizardMeta from "@m-kgl/dnd-pixel-sprites/sprites/wizard-female.json";

Asset format

Per class + gender: sprites/<class>-<gender>.png (384×64 sheet, 6 frames @ 64×64, 8 fps) + <class>-<gender>.json metadata:

{
  "id": "wizard-female",
  "className": "wizard",
  "gender": "female",
  "frameWidth": 64, "frameHeight": 64, "frameCount": 6, "fps": 8,
  "animations": { "idle": { "from": 0, "to": 5, "fps": 8 } },
  "anchor": { "x": 32, "y": 60 },
  "hitbox": { "x": 22, "y": 24, "w": 20, "h": 34 },
  "palette": ["#f0d2af", "..."],
  "colorSlots": { "skin": ["#f0d2af", "#c3a078"], "hair": ["..."], "...": ["..."] },
  "tags": ["dnd5e", "class", "spellcaster", "female"]
}

sprites/manifest.json lists all 20 sprites. All pixels are fully opaque (no anti-aliasing) except the semi-transparent ground shadow — that's what makes the palette swap exact.

Regenerating sprites

The sprites are generated programmatically (no binary editing needed):

npm run generate   # writes sprites/*.png + *.json + manifest.json
npm run verify     # sanity check: dimensions, animation, metadata
npm run build      # tsup → dist/ (ESM + CJS + .d.ts)

Generator source lives in scripts/ (palettes, body parts, per-class composition) and is not shipped in the package.

Demos

npm run demo
# → http://localhost:5234            vanilla canvas grid (all 20 sprites)
# → /react.html                      <DndSprite> playground
# → /picker.html                     picker + card + language switch (EN/DE)
# → /health.html                     HealthBar (all variants/states)
# → /sprite-health.html              sprite reacting to HP
# → /colors.html                     color picker + palette swap

Gender differences

Female variants are not recolors — they have distinct geometry: narrower V-shaped head with eyelashes and lips, slimmer neck and arms, waisted torso, wider hips, class-specific outfit cuts (waist + belt accents) and hairstyles (e.g. ponytail). Male variants have broader heads, square jaws, straight torsos and beards on several classes.

Migration from 0.3.x (German API)

The whole API switched to English in 0.4.0. Mapping (old → new):

| 0.3.x (German) | 0.4.0 (English) | |---|---| | klasse / DndKlasse / "barbar", "kaempfer", "magier", ... | characterClass / DndClass / "barbarian", "fighter", "wizard", ... | | geschlecht / "maennlich" / "weiblich" | gender / "male" / "female" | | farben / SpriteFarben / FarbAuswahl | colors / SpriteColors / ColorPicker | | Slots haut, haare, metall, ... | skin, hair, metal, ... | | zustand / HealthZustand / "voll"..."sterbend" | healthState / HealthState / "full"..."dying" | | KlassenAttribute / attributeDaten | ClassAttributes / attributeData | | sliderAnzeigen, dotsAnzeigen, bestaetigenButtonAnzeigen, ... | showSlider, showDots, showConfirmButton, ... | | AuswahlErgebnis / onAuswahl | SelectionResult / onConfirm | | setFarben() / faerbeSpriteUm() | setColors() / recolorSprite() | | Asset ids barbar-maennlich.png | barbarian-male.png | | CSS .dnd-hp__fuellung, .dnd-farben, data-zustand, ... | .dnd-hp__fill, .dnd-colors, data-state, ... |

Also new in 0.4.0: components are styled by default (previously you had to wire classNames manually) — pass unstyled to get the old unstyled behavior, and language="de" for German UI texts.

Architecture

src/
├── index.ts          # vanilla entry
├── DndSprite.ts      # abstract sprite class (load/update/draw/recolor)
├── registry.ts       # DND_CLASSES, createSprite()
├── recolor.ts        # palette swap engine
├── assets.ts         # PNG/JSON references (import.meta.url)
├── classes/          # 10 thin subclasses
└── react/
    ├── index.ts          # react entry
    ├── DndSprite.tsx     # canvas component (+ health effects)
    ├── classes.tsx       # 10 typed components
    ├── DndCharacterPicker.tsx
    ├── DndSpriteCard.tsx
    ├── HealthBar.tsx
    ├── ColorPicker.tsx
    ├── ClassAttributes.tsx
    ├── attributeData.ts  # 5e class data (EN + DE)
    ├── i18n.ts           # UI text dictionaries
    ├── useDndSprite.ts   # hooks
    └── theme.css         # default theme (dnd-* namespace)

License

MIT