How EmojiEmoji
A Japanese word (็ตตๆๅญ) meaning 'picture character' โ small graphical symbols used in digital communication to express ideas, emotions, and objects. 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 standardUnicode Standard
The complete character encoding system maintained by the Unicode Consortium, defining characters, properties, algorithms, and encoding forms., 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 pointCode Point
A unique numerical value assigned to each character in the Unicode standard, written in the format U+XXXX (e.g., U+1F600 for ๐).. 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 EmojiColor Emoji Full-color emoji rendered using bitmap images or color vector graphics, as opposed to monochrome text-style rendering. |
sbix (PNG bitmaps) |
| Windows | Segoe UI Emoji | COLR/CPALCOLR/CPAL (COLR) OpenType color font tables that define emoji as layered vector shapes with a color palette, used by Windows and Chrome. v0 (vector) |
| Android | Noto Color Emoji | CBDT/CBLCCBDT/CBLC (CBDT) Color Bitmap Data Table and Color Bitmap Location Table โ OpenType tables for embedding bitmap color emoji in fonts. (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 fontEmoji Font
A digital font file containing color emoji glyph designs, using technologies like COLR, CBDT, SVG, or sbix for rendering. for characters that have no glyph in the active text font.
How the Rendering Pipeline Works
1. UnicodeUnicode
Universal character encoding standard that assigns a unique number to every character across all writing systems and symbol sets, including emoji. Code Point Resolution
The text string arrives as a sequence of Unicode code points. For ZWJZero Width Joiner (ZWJ)
An invisible Unicode character (U+200D) used to join multiple emoji into a single composite emoji, such as combining people and objects into profession emoji. 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 vsVariation Selector (VS)
Unicode characters (VS-15 U+FE0E and VS-16 U+FE0F) that modify whether a character renders in text (monochrome) or emoji (colorful) presentation.. 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
An open-source emoji set originally created by Twitter, providing SVG and PNG emoji assets that can be used in any project. (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:
- Tofu (โฏ): A blank rectangle, the classic missing glyph indicator
- Component decomposition: A ZWJ sequence like ๐งโ๐ (astronaut) breaks into ๐ง + ๐
- Previous version rendering: An older, less detailed version of the character
- Text presentationText Presentation
The rendering of a character as a monochrome text symbol, either by default or when Variation Selector-15 is applied.: 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>☎</span>
<!-- Forces emoji presentationEmoji Presentation
The default rendering of a character as a colorful emoji glyph, either inherently or when triggered by Variation Selector-16. โ๏ธ -->
<span>☎️</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:
- macOS Safari โ Apple Color Emoji, WebKit rendering
- Windows Chrome โ Segoe UI Emoji, Blink rendering
- Android Chrome โ Noto Color Emoji, Blink rendering
- iOS Safari โ Apple Color Emoji, WebKit rendering
- 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
- Compare how emoji look across platforms: Compare Tool
- Analyze ZWJ sequences and their components: Sequence Analyzer
- Browse Unicode emoji terminology: Glossary
- Query emoji data programmatically: API Reference