Skip to content

Darker-Ink/sprites

Repository files navigation

emoji-sprites

Pre-rendered emoji sprite sheets for Fluent UI Emoji, Noto Emoji, and Twemoji, plus typed emoji data and small lookup helpers. The set is curated (defined in data/emojis.json); Twemoji backfills gaps (mostly flags) where Fluent UI and Noto don't ship coverage.

Install

npm install emoji-sprites

Requires Node 22+ (for native JSON imports) or any modern bundler (Vite / webpack 5+ / Rollup / Bun).

Usage

import {
  emojis,
  meta,
  findEmoji,
  tilePosition,
  spriteUrl,
} from "emoji-sprites";

const wave = findEmoji("👋");
const tile = tilePosition(wave);     // { sheet: "skin/1", x, y, size: 80 }
const url  = spriteUrl("twemoji", tile.sheet);

// CSS:
// background-image: url(${url});
// background-position: -${tile.x}px -${tile.y}px;
// width: ${tile.size}px; height: ${tile.size}px;

Lookup helpers

findBySurrogate("👋");       // emoji where .surrogates === "👋"
findByName("grinning");      // matches any of emoji.names (colons stripped)
findByName(":wave:");        // also handles :name: form
findByEmoticon("<3");        // resolves emoticon → name → emoji
findEmoji(input);            // tries surrogate, name, emoticon in that order

Search

For type-as-you-go / fuzzy search (typo tolerance, multi-word AND), use search — backed by Fuse.js over the primary names plus the keyword synonyms in data/secondary.json:

import { search, warmSearchIndex } from "emoji-sprites";

search("smiling face");                  // 😊 😄 😃 ...
search("smily");                         // typo-tolerant → 🙂 ☺️ 😄 ...
search("smil");                          // prefix → 🙂 ☺️ 😄 ...
search("waving hand");                   // 👋
search("party", { limit: 5 });

warmSearchIndex();                       // optional: pay one-time setup early

The Fuse instance is built lazily on the first search() call (~5–10 ms one-time, ~2–10 ms steady-state). Skin-tone variants are excluded — pass the parent through tilePosition and pick a tone separately if needed.

Sprites

tilePosition(emoji) returns the sprite sheet identifier and pixel offset:

type TilePosition = {
  sheet: "base" | "skin/1" | "skin/2" | "skin/3" | "skin/4" | "skin/5" | "skin/6";
  x: number;
  y: number;
  size: number; // always 80
};

spriteUrl(provider, sheet) returns a file:// URL (Node) or asset URL (bundler). spritePath(provider, sheet) returns an absolute filesystem path (Node only). Both accept an optional third format argument, "png" (default) or "webp", so you can opt into the smaller lossless WebP variant when bytes matter:

spriteUrl("twemoji", "base");          // → .../base_sprites.png
spriteUrl("twemoji", "base", "webp");  // → .../base_sprites.webp

For bundlers that prefer direct asset imports, every sheet is exposed in both formats via the exports map:

import twemojiBase     from "emoji-sprites/sprites/twemoji/base";        // PNG (default)
import twemojiBasePng  from "emoji-sprites/sprites/twemoji/base.png";
import twemojiBaseWebp from "emoji-sprites/sprites/twemoji/base.webp";
import notoSkin1Webp   from "emoji-sprites/sprites/noto-emoji/skin/1.webp";

Subpath imports

Tree-shakeable subpaths are available so you only pay for what you use:

import { emojis }       from "emoji-sprites/data";
import { meta }         from "emoji-sprites/meta";
import { tilePosition } from "emoji-sprites/sprites";
import { findEmoji }    from "emoji-sprites/lookup";
import { search }       from "emoji-sprites/search";
import type { Emoji }   from "emoji-sprites/types";

Raw JSON is also reachable directly:

import emojis     from "emoji-sprites/data/emojis.json"     with { type: "json" };
import secondary  from "emoji-sprites/data/secondary.json"  with { type: "json" };
import emoticons  from "emoji-sprites/data/emoticons.json"  with { type: "json" };

Sprite layout

Each provider ships a base sheet (42 × 39 grid) and six skin-tone sheets (10 × 31 each), all at an 80×80 cell size. Every sheet is emitted as both .png (lossless, broadly compatible) and .webp (lossless, ~30–50 % smaller):

Sheet File Tone
base base_sprites.{png,webp} non-skin emoji
skin/1 skin_tone_1.{png,webp} default
skin/2 skin_tone_2.{png,webp} light (1F3FB)
skin/3 skin_tone_3.{png,webp} medium-light
skin/4 skin_tone_4.{png,webp} medium
skin/5 skin_tone_5.{png,webp} medium-dark
skin/6 skin_tone_6.{png,webp} dark (1F3FF)

Constants are exposed on meta:

meta // {
//   cell: 80,
//   base: { cols: 42, rows: 39 },
//   skin: { cols: 10, rows: 31 },
//   providers: ["twemoji", "fluentui-emoji", "noto-emoji"],
// }

Regenerating sprites (development)

The sprites are produced by a Bun CLI under src/ that reads SVGs from upstream submodules under vendor/.

git submodule update --init --recursive
bun install
bun generate           # writes ./sprites/<provider>/*.{png,webp}
bun generate --help

Custom additions

A handful of emoji aren't natively shipped by Fluent UI or Noto (mostly flags, and regional letters and a few state-flag combos). For those, an SVG dropped into fallbacks/<provider>/ will be picked up before falling through to Twemoji as a final source — keeping the grid complete across all three sets.

Data

The metadata in data/ is scraped from public web sources. It's bundled here for convenience; the upstream emoji art remains the canonical source for the actual graphics.

Licenses & Attribution

The code in this repo is MIT licensed (see LICENSE). The emoji artwork lives in the submodules under vendor/ and is owned by its respective creators — if you ship these sprites, you inherit those terms:

This README serves as the attribution notice for Twemoji (CC-BY 4.0) and Noto Emoji (OFL) as bundled in this repository. The artwork has not been modified; the SVGs are read directly from upstream and packed into sprite sheets.

About

Pre-rendered emoji sprite sheets for Twemoji, Fluent UI Emoji, and Noto Emoji

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors