How Emoji Rendering Works Across Platforms: Font Stacks and Fallbacks

How Emoji Rendering Works Across Platforms

Open the same message on macOS, Windows, and Android and the 🍕 pizza emoji will look noticeably different on each. That's not a bug — it's by design. Emoji are part of the Unicode standard, which specifies which characters exist and what they mean, but leaves the visual design entirely up to platform vendors.

Understanding how rendering works under the hood helps you build apps that look good everywhere and degrade gracefully when an emoji is not available.

The Font Stack for Emoji

When the operating system needs to render a character, it walks a font stack — an ordered list of fonts — until it finds a glyph for that code point. Emoji fonts are typically placed near the end of the stack, after all text fonts.

System Emoji Fonts

Platform Font Format
macOS / iOS Apple Color Emoji sbix (PNG bitmaps)
Windows Segoe UI Emoji COLR/CPALCOLR/CPAL (COLR)
絵文字をカラーパレットを持つ重ね合わせたベクターシェイプとして定義するOpenTypeカラーフォントテーブル。WindowsとChromeで使用されている。
v0 (vector)
Android Noto Color Emoji CBDT/CBLCCBDT/CBLC (CBDT)
フォントファイルにビットマップカラー絵文字を埋め込むためのOpenTypeテーブル「Color Bitmap Data Table」と「Color Bitmap Location Table」。
(PNG bitmaps)
Linux Noto Color Emoji (common) CBDT/CBLC or COLRv1
ChromeOS Noto Color Emoji CBDT/CBLC

The font format determines rendering quality at different sizes. COLR-based fonts scale cleanly as vectors; bitmap-based fonts (sbix, CBDT) look sharp at their designed resolution but can appear blurry when scaled up significantly.

CSS Font Stack

In web applications, you can hint at emoji fonts via CSS:

body {
  font-family:
    /* System UI font first */
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    Roboto,
    /* Emoji fonts — browser picks available one */
    "Apple Color Emoji",
    "Segoe UI Emoji",
    "Noto Color Emoji",
    sans-serif;
}

Browsers handle emoji rendering semi-automatically. Even without explicit emoji fonts in the stack, the browser falls back to the OS emoji font for characters that have no glyph in the active text font.

How the Rendering Pipeline Works

1. Unicode Code Point Resolution

The text string arrives as a sequence of Unicode code points. For ZWJゼロ幅接合子(ZWJ) (ZWJ)
複数の絵文字を1つの複合絵文字に結合するために使われる不可視のUnicode文字(U+200D)。人物と物体を組み合わせた職業絵文字などに使用される。
sequences like 👨‍👩‍👧‍👦 (family), the shaping engine must recognize the full sequence before deciding which glyph to use.

2. Shaping

A text shaping library (HarfBuzz on most platforms; Core Text on Apple) processes the sequence. For emoji:

  • It checks if the font supports the full ZWJ sequence as a ligature
  • If not, it falls back to rendering individual components
  • Variation selectors (U+FE0F for emoji, U+FE0E for text) influence which glyph is chosen

3. Glyph Selection

The shaper queries the font's GSUB (Glyph Substitution) table or a platform-specific lookup to find the appropriate glyph or glyph sequence.

4. Rasterization

For bitmap fonts (sbix, CBDT), the engine picks the closest pre-rendered PNG at a size matching the requested display size. For vector fonts (COLR), it renders the layered vector paths with their associated colors.

Platform Visual Differences

The same emoji character can differ in:

  • Shape: 😂 on Apple has distinctive teardrop placement vs異体字セレクタ (VS)
    文字をテキスト形式(モノクロ)で表示するかどうかを切り替えるUnicode文字。VS-15(U+FE0E)がテキスト表示、VS-16(U+FE0F)が絵文字表示を指定する。
    . Google's version
  • Color palette: 🔥 fire emoji ranges from yellow-orange (Apple) to red-heavy (Samsung)
  • Detail level: 🍣 sushi on Apple shows realistic nigiri; older Android versions showed a cartoon version
  • Gender and skin tone rendering: Some platforms differ in default presentation for gender-neutral sequences

Checking Platform Differences

To inspect how a specific emoji renders on different platforms, compare the SVG/PNG source files:

# Noto Emoji (Google) is open source
git clone https://github.com/googlefonts/noto-emoji
ls noto-emoji/png/128/  # PNG files named by code point

# TwemojiTwemoji
Twitterが作成したオープンソースの絵文字セット。SVGおよびPNG形式の絵文字素材を提供しており、あらゆるプロジェクトで利用可能。
(Twitter/X) is also open source # Files are named by hex code point, e.g. 1f602.svg for 😂

Fallback Rendering: When an Emoji Is Not Supported

When a platform does not have a glyph for a code point or sequence, it falls back in one of these ways:

  1. Tofu (▯): A blank rectangle, the classic missing glyph indicator
  2. Component decomposition: A ZWJ sequence like 🧑‍🚀 (astronaut) breaks into 🧑 + 🚀
  3. Previous version rendering: An older, less detailed version of the character
  4. Text presentation: The character renders as a symbol without color

Detecting Support in JavaScript

function supportsEmoji(emoji) {
  const canvas = document.createElement('canvas');
  canvas.width = canvas.height = 1;
  const ctx = canvas.getContext('2d');

  // Draw a known unsupported character first (baseline)
  ctx.fillText('\uD83E\uDD37', -4, 4); // 🤷 as baseline
  const baseline = ctx.getImageData(0, 0, 1, 1).data;

  // Clear and draw the emoji we're testing
  ctx.clearRect(0, 0, 1, 1);
  ctx.fillText(emoji, -4, 4);
  const tested = ctx.getImageData(0, 0, 1, 1).data;

  // If pixel data differs, the emoji is likely rendered
  return baseline[0] !== tested[0] || baseline[1] !== tested[1];
}

console.log(supportsEmoji('🥹')); // true on recent platforms
console.log(supportsEmoji('\u{1FAE0}')); // newer emoji, may be false on older OS

Note: this canvas technique is not 100% reliable across all browsers, but works well enough for graceful degradation decisions.

Web Rendering Considerations

Variation Selector-16

Many symbols need an explicit U+FE0F (VS16) to render as emoji in browsers. Without it, they may appear as monochrome text glyphs:

<!-- May render as text ☎ -->
<span>&#x260E;</span>

<!-- Forces emoji presentation ☎️ -->
<span>&#x260E;&#xFE0F;</span>

Emoji and Line Height

Color emoji fonts often have metrics that push line height higher than your text font. Apply consistent sizing:

.emoji {
  font-size: 1em;
  line-height: 1;
  vertical-align: -0.1em; /* fine-tune per font */
}

Using Image Fallbacks

For consistent cross-platform display (common in chat applications), replace emoji with images from a standardized set:

import twemoji from 'twemoji';

// Replace all emoji in element with Twemoji SVG images
twemoji.parse(document.body, {
  folder: 'svg',
  ext: '.svg',
  base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/'
});

This ensures pixel-perfect consistency but increases DOM complexity and disables native copy-paste of text.

Native App Considerations

iOS and macOS

Apple's emoji font is private and ships with the OS. You cannot substitute it. All emoji rendering goes through Core Text, which handles ZWJ sequences, skin tone modifiers, and variation selectors automatically.

Android

Android ships Noto Color Emoji. Older Android versions (pre-4.4) had very limited emoji support. Apps targeting older API levels should either: - Use a bundled emoji font via EmojiCompat (Jetpack library) - Fall back to image assets

// Jetpack EmojiCompat for backward compatibility
val config = BundledEmojiCompatConfig(context)
EmojiCompat.init(config)

// In layout XML
<androidx.emoji2.widget.EmojiTextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello 👋" />

Testing Your Rendering

A practical testing matrix for emoji rendering:

  1. macOS Safari — Apple Color Emoji, WebKit rendering
  2. Windows Chrome — Segoe UI Emoji, Blink rendering
  3. Android Chrome — Noto Color Emoji, Blink rendering
  4. iOS Safari — Apple Color Emoji, WebKit rendering
  5. Linux Firefox — Noto (or system), Gecko rendering

Use browser developer tools to inspect which font serves a given glyph: in Chrome DevTools, the Computed panel shows the resolved font for selected text.

Explore More on EmojiFYI

関連ツール

🔀 プラットフォーム比較 プラットフォーム比較
Apple、Google、Samsung、Microsoft などのプラットフォームで絵文字がどのように表示されるかを比較してみましょう。視覚的な違いを並べて確認できます。
🔍 シーケンスアナライザー シーケンスアナライザー
ZWJ シーケンス、肌の色の修飾子、キーキャップシーケンス、国旗のペアを個別のコンポーネントに分解します。

用語集の項目

CBDT/CBLC (CBDT) CBDT/CBLC (CBDT)
フォントファイルにビットマップカラー絵文字を埋め込むためのOpenTypeテーブル「Color Bitmap Data Table」と「Color Bitmap Location Table」。
COLR/CPAL (COLR) COLR/CPAL (COLR)
絵文字をカラーパレットを持つ重ね合わせたベクターシェイプとして定義するOpenTypeカラーフォントテーブル。WindowsとChromeで使用されている。
Twemoji Twemoji
Twitterが作成したオープンソースの絵文字セット。SVGおよびPNG形式の絵文字素材を提供しており、あらゆるプロジェクトで利用可能。
Unicode規格 Unicode規格
Unicodeコンソーシアムが管理する包括的な文字符号化システム。文字・プロパティ・アルゴリズム・符号化形式を定義している。
カラー絵文字 カラー絵文字
ビットマップ画像またはカラーベクターグラフィックを使って表示されるフルカラーの絵文字。モノクロのテキスト表示とは対照的。
コードポイント コードポイント
Unicode規格で各文字に割り当てられた固有の数値。U+XXXX形式で表記される(例:😀はU+1F600)。
ゼロ幅接合子(ZWJ) (ZWJ) ゼロ幅接合子(ZWJ) (ZWJ)
複数の絵文字を1つの複合絵文字に結合するために使われる不可視のUnicode文字(U+200D)。人物と物体を組み合わせた職業絵文字などに使用される。
テキスト表示 テキスト表示
文字をモノクロのテキスト記号として表示するレンダリング。デフォルトでテキスト形式の場合、またはVariation Selector-15が適用された場合に使用される。
ユニコード ユニコード
すべての文字体系と絵文字を含む、あらゆる文字にユニークな番号を割り当てる汎用文字符号化規格。
異体字セレクタ (VS) 異体字セレクタ (VS)
文字をテキスト形式(モノクロ)で表示するかどうかを切り替えるUnicode文字。VS-15(U+FE0E)がテキスト表示、VS-16(U+FE0F)が絵文字表示を指定する。
絵文字 絵文字
「絵文字」は「えもじ」と読む日本語で「絵+文字」を意味する。デジタルコミュニケーションでアイデア・感情・物事を表現するために使われる小さなグラフィック記号。
絵文字フォント 絵文字フォント
カラー絵文字のグリフデザインを含むデジタルフォントファイル。レンダリングにはCOLR・CBDT・SVG・sbixなどの技術が使用される。
絵文字表示 絵文字表示
文字をカラフルな絵文字グリフとして表示するデフォルトのレンダリング。本来的に絵文字として定義されているか、Variation Selector-16によって指定される。

関連ストーリー