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.
npm install emoji-spritesRequires Node 22+ (for native JSON imports) or any modern bundler (Vite / webpack 5+ / Rollup / Bun).
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;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 orderFor 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 earlyThe 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.
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.webpFor 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";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" };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"],
// }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 --helpA 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.
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.
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:
- Fluent UI Emoji — Copyright © Microsoft Corporation, MIT.
- Noto Emoji — Copyright © 2013 Google LLC, SIL Open Font License 1.1. "Noto" is a trademark of Google LLC.
- Twemoji — Copyright © Twitter, Inc and other contributors. Graphics under CC-BY 4.0; code under MIT. See the attribution requirements.
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.