How to emoji rendering: os fonts vs color font formats
- Step 1Start at the Unicode layer — Every emoji is one or more Unicode codepoints — `😀` is `U+1F600`. Unicode fixes the meaning and the name (`GRINNING FACE`), never the appearance. Two platforms agreeing on the codepoint is what lets the emoji survive being copied between them.
- Step 2Find the OS emoji font — When the text engine hits an emoji codepoint, it looks it up in the platform's emoji font: Apple Color Emoji on iOS/macOS, Noto Color Emoji on Android, Segoe UI Emoji on Windows. Each is a completely separate set of drawings.
- Step 3Identify the colour format — Open the font and the colour data is stored in one of four OpenType tables — `sbix` (Apple, PNG bitmaps), `CBDT/CBLC` (Google, PNG bitmaps), `COLR/CPAL` (Microsoft, layered vectors), or `SVG`. The format determines whether the emoji scales crisply or pixelates when enlarged.
- Step 4Trace the fallback chain — If the active font lacks a glyph for the codepoint, the OS walks an ordered font stack until something provides it; if nothing does, you get tofu (□). Use the [Character Coverage Map](/font-tools/character-coverage-map) to confirm whether a font covers the emoji blocks you need.
The four color-font formats
All four are optional OpenType tables layered on top of a normal font. A single font can even carry more than one for cross-platform reach.
| Format | Glyph data | Vendor origin | Scales cleanly? |
|---|---|---|---|
sbix | PNG bitmaps at fixed sizes | Apple | No — bitmap, blurs when enlarged past its largest strike |
CBDT / CBLC | PNG bitmaps (data + location tables) | No — bitmap, same strike limitation | |
COLR / CPAL | Layered vector glyphs + colour palette | Microsoft | Yes — vector, resolution-independent |
SVG | Embedded SVG documents per glyph | Adobe / Mozilla | Yes — vector, but heaviest to parse |
Which emoji font ships with each platform
This is why a copied 😬 looks different the moment it lands on another OS.
| Platform | Default emoji font | Format |
|---|---|---|
| iOS / macOS | Apple Color Emoji | sbix (bitmap) |
| Android | Noto Color Emoji | CBDT/CBLC (bitmap); newer builds add COLRv1 vector |
| Windows | Segoe UI Emoji | COLR/CPAL (vector) |
| Linux (common) | Noto Color Emoji | CBDT/CBLC, or COLRv1 on recent distros |
| Firefox (any OS) | Bundled fallback / OS font | Supports COLR, sbix, CBDT, and SVG |
COLRv0 vs COLRv1
COLR is the only mainstream colour format that is both vector and broadly supported. v1 is the modern target.
| Capability | COLRv0 | COLRv1 |
|---|---|---|
| Layered solid colours | Yes | Yes |
| Gradients (linear / radial) | No | Yes |
| Transforms & compositing | No | Yes |
| Browser support | Wide (older Chrome/Edge/Firefox) | Chrome/Edge 98+, Firefox, Safari (recent) |
Cookbook
Practical recipes for controlling emoji rendering on the web. The OS font is the default; these are the levers you have when you need consistency.
Let the OS render emoji (the default, and usually correct)
ExampleAn emoji-first font stack: the browser tries the platform emoji font before falling back to your text font. No download, always current.
body {
font-family: "Inter", "Apple Color Emoji", "Segoe UI Emoji",
"Noto Color Emoji", sans-serif;
}Force one consistent emoji set across all platforms
ExampleEmbed a color font (e.g. an open COLR/SVG emoji set) so every visitor sees identical artwork. Costs a download; support for colour formats still varies by browser.
@font-face {
font-family: "MyEmoji";
src: url("/fonts/myemoji.woff2") format("woff2");
}
.emoji { font-family: "MyEmoji", "Apple Color Emoji", sans-serif; }Detect whether color emoji are available in JS
ExampleRender an emoji to a canvas and check whether any non-greyscale pixels appear — a pragmatic feature test when you must decide between native and image-based emoji.
function hasColorEmoji() {
const c = document.createElement("canvas");
c.width = c.height = 16;
const ctx = c.getContext("2d");
ctx.textBaseline = "top";
ctx.font = "16px sans-serif";
ctx.fillText("\u{1F600}", 0, 0);
const { data } = ctx.getImageData(0, 0, 16, 16);
for (let i = 0; i < data.length; i += 4) {
if (data[i] !== data[i + 1] || data[i + 1] !== data[i + 2]) return true;
}
return false;
}Shrink a webfont by stripping emoji you do not use
ExampleIf you embed a font that bundles emoji glyphs you never show, remove them with the Emoji Remover — emoji blocks (U+1F300–1FAFF) and the Private Use Area are common bloat.
Drop the font into /font-tools/emoji-remover → strips U+1F300–U+1FAFF (emoji), U+2600–U+27BF (dingbats), and the Private Use Area, keeping all text glyphs.
Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
An emoji shows as a hollow box (□, 'tofu')
warnNo font in the fallback chain has a glyph for that codepoint — usually a newer emoji on an older OS. The character is intact; only the rendering is missing.
A ZWJ sequence (e.g. 👨👩👧) renders as separate emoji
warnThe platform lacks a single ligature glyph for the Zero-Width-Joiner sequence, so it falls back to drawing each component codepoint individually.
A skin-tone modifier is ignored
warnThe base emoji renders but the Fitzpatrick modifier (U+1F3FB–1F3FF) is not supported for that glyph, so the default yellow appears.
A flag emoji shows as two letters on Windows
warnWindows historically does not draw national flag emoji; the two Regional Indicator codepoints fall back to letter glyphs (e.g. 🇬🇧 → GB).
An embedded COLR color font appears monochrome
warnThe browser supports the font but not that colour format/version (e.g. COLRv1 on an old engine), so it renders the base outline layer only.
Emoji look pixelated when scaled up large
okThe font is bitmap-based (sbix or CBDT/CBLC) and you have exceeded its largest strike. Vector formats (COLR, SVG) avoid this.
Emoji vanish entirely in a PDF or print export
errorThe emoji font was not embedded and the renderer has no colour-emoji fallback. Embed an emoji font or rasterise the emoji to an image first.
Frequently asked questions
Why does the same emoji look different on iPhone and Android?
Because each platform ships a different emoji font with its own artwork — Apple Color Emoji on iOS, Noto Color Emoji on Android. The Unicode codepoint is identical; only the font that draws it changes.
What are the four color-font formats?
sbix (Apple, PNG bitmaps), CBDT/CBLC (Google, PNG bitmaps), COLR/CPAL (Microsoft, layered vectors), and SVG (Adobe/Mozilla, embedded SVG). They differ in whether the colour data is bitmap or vector, which decides whether the emoji scales cleanly.
Which is better, bitmap or vector color fonts?
Vector formats (COLR, SVG) scale to any size without blurring and are usually smaller, which is why the industry is moving toward COLRv1. Bitmap formats (sbix, CBDT/CBLC) were the original approach and look perfect at their built-in sizes but pixelate when enlarged beyond them.
Can I force my own emoji font on a website?
Yes — embed a color font with @font-face and put it first in your font-family stack. Every visitor then sees the same artwork. The trade-offs are the extra download and that browser support for colour formats still varies, so keep the OS emoji fonts in the stack as a fallback.
What is COLRv1 and why does it matter?
COLRv1 extends the COLR format with gradients, transforms, and compositing, so a single small vector font can render rich, scalable colour emoji. It is supported in Chrome/Edge 98+ and recent Firefox/Safari, and Google now ships a COLRv1 build of Noto Color Emoji as a lightweight vector alternative to the large bitmap version.
Why is Noto Color Emoji so large as a download?
The traditional CBDT/CBLC build embeds a PNG for every emoji at multiple sizes, which adds up to several megabytes. The newer COLRv1 vector build is dramatically smaller because it stores shapes and gradients instead of bitmaps.
What is 'tofu' (the empty box)?
Tofu (□) is the fallback glyph shown when no font in the chain has a drawing for a codepoint — typically a newer emoji on an older OS. The text data is preserved; only the visual is missing, and it usually appears once the OS is updated. The Noto project is literally named after eliminating tofu ('no-tofu').
How do ZWJ emoji sequences work?
Some emoji are built by joining several codepoints with a Zero-Width Joiner (U+200D) — for example 👨💻 is 'man' + ZWJ + 'laptop'. If the font has a ligature glyph for the whole sequence it draws one combined emoji; if not, it falls back to drawing each part separately.
How can I tell which emoji a font actually supports?
Run the font through the Character Coverage Map to see which Unicode blocks (including the emoji ranges) it covers, or use the Glyph Inspector to view individual glyphs and their codepoints.
Do emoji affect my webfont file size?
They can, heavily — if a font bundles colour-emoji glyphs you never display, you are shipping bitmaps for nothing. The Emoji Remover strips emoji and Private-Use-Area glyphs while keeping every text character; see also Remove emoji from a font online.
Why do emoji break in PDFs and printed output?
Print and PDF pipelines often have no colour-emoji fallback and do not embed the OS emoji font, so emoji disappear or render as outlines. Embed an emoji font in the document, or convert the emoji to inline images before export.
Is there a way to make emoji identical everywhere?
Only by taking control away from the OS: embed one color font for all platforms, or replace emoji with images (the approach Twitter's open-source Twemoji set popularised). Both guarantee consistency at the cost of a download and some loss of the native feel.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.