
How to use emoji in web typography?
- Sajjad
- Typography
- 11 Oct, 2026
To use emoji well in web typography, treat them as text: they're Unicode characters drawn by colour fonts, so they follow your font stack, size and line height like any other glyph. Put a dedicated emoji font at the end of your font-family list so emoji render consistently, control whether ambiguous characters appear in colour or as plain symbols with variation selectors or font-variant-emoji, and size them in em so they scale with surrounding text. Remember that a single visible emoji can be several code points long, which affects character counts and truncation in JavaScript. Most importantly, make sure emoji don't replace words: screen readers announce their full names, so decorative emoji should be hidden and meaningful ones labelled.
Emoji appear everywhere from product names and headings to user comments and marketing emails, and they render differently on every platform. In this article you'll learn how emoji fonts work, how to write a font stack that handles them, how to choose between colour and text presentation, how to align and size emoji in running text, how to count and truncate strings that contain them, and how to keep them accessible.
How Emoji Are Rendered
An emoji is a Unicode character, or a sequence of characters, just like a letter. The thumbs-up emoji is U+1F44D. What makes it look like an emoji is the font that draws it. Each major platform ships its own colour emoji font:
| Platform | Default emoji font | Notes |
|---|---|---|
| macOS and iOS | Apple Color Emoji | Bitmap images stored in the font |
| Windows 10 and 11 | Segoe UI Emoji | Colour vector glyphs |
| Android and ChromeOS | Noto Color Emoji | Open-source, also available on Google Fonts |
| Most Linux desktops | Noto Color Emoji | Usually installed with the system |
Because the fonts are different, the same emoji can look noticeably different on each device. A smiley that reads as friendly on one platform may look sarcastic on another. If precise appearance matters, you either load your own emoji font or use images, but for everyday text the system fonts are usually the right choice.
Colour Font Formats
Ordinary fonts describe glyphs as outlines filled with one colour. Emoji fonts use colour font technologies built into the OpenType format:
- sbix: Apple's format, which stores a bitmap image for each glyph at several sizes.
- CBDT and CBLC: Google's bitmap format, historically used by Noto Color Emoji.
- COLR and CPAL: Vector layers with a colour palette. Version 1 of COLR adds gradients and transformations, and newer releases of Noto Color Emoji use it.
- SVG in OpenType: SVG documents embedded in the font, supported mainly by Firefox and Safari.
Browser support for these formats varies, which is one reason not to self-host an emoji font casually. The system font on each platform is guaranteed to work there.
Writing a Font Stack That Handles Emoji
When the browser renders a character, it uses the first font in your font-family list that contains a glyph for it. Most text fonts don't contain emoji, so the browser falls back through the list, and if nothing matches, to whatever the system provides.
That usually works, but some text fonts include monochrome versions of common symbols such as the heart, the check mark or the star. If your text font contains them, you'll get a black outline symbol instead of a colour emoji. Listing emoji fonts explicitly at the end of the stack makes behaviour more predictable.
:root {
--font-emoji:
"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol";
}
body {
font-family: "Inter", system-ui, sans-serif, var(--font-emoji);
}
Keep the emoji fonts after your text fonts. If you put an emoji font first, it may supply its own glyphs for digits, the hash sign and the asterisk, because those characters have emoji forms too, and your numbers can suddenly look different.
CSS also defines a generic emoji font family, which you can add at the end for browsers that support it:
body {
font-family: "Inter", system-ui, sans-serif, var(--font-emoji), emoji;
}
Loading Noto Color Emoji
If you need consistent emoji on every platform, Noto Color Emoji is available on Google Fonts, which serves it in small unicode-range slices so users download only the emoji a page uses. Load it as you would any font, and list it before the system emoji fonts.
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap"
/>
body {
font-family:
"Inter", system-ui, sans-serif, "Noto Color Emoji", "Apple Color Emoji",
"Segoe UI Emoji";
}
Consider the cost. Consistency across platforms is nice for a chat product or an emoji picker, but on a typical content site, users already know how emoji look on their own device.
Colour Emoji or Text Symbols
Many characters have two possible presentations. The heart (U+2764), the warning sign, the check mark and many arrows existed in Unicode long before emoji, and can be drawn either as a plain text symbol or as a colour emoji. The default depends on the character and the platform.
Variation Selectors
Unicode lets you request a presentation by adding an invisible character straight after the symbol:
- U+FE0F (VS16): Requests emoji presentation, in colour.
- U+FE0E (VS15): Requests text presentation, usually monochrome and following the text colour.
<p>Text heart: ❤︎ and emoji heart: ❤️</p>
<p>Plain arrow for UI: ↔︎</p>
Platforms don't always honour VS15, particularly for characters that are emoji-only by default, so test the characters you rely on.
font-variant-emoji
Newer browsers support the font-variant-emoji property, which applies the same choice from CSS without editing the text:
.status-list {
font-variant-emoji: text;
}
.reaction-bar {
font-variant-emoji: emoji;
}
The values are normal, text, emoji and unicode, where unicode follows the Unicode default for each character. Browsers that don't support the property ignore it, so treat it as an enhancement and keep variation selectors in the content where the presentation really matters.
Sizing and Aligning Emoji in Text
Emoji sit in the line like letters, but their designs are square and fill much more of the em box than lowercase letters. In body text, an emoji at the same font size can look heavy and slightly tall.
.emoji {
font-size: 1.1em;
line-height: 1;
vertical-align: -0.1em;
}
h1 .emoji {
font-size: 0.9em;
}
<h1>
Launch week <span class="emoji" role="img" aria-label="rocket">π</span>
</h1>
<p>Thanks for the feedback <span class="emoji" aria-hidden="true">π</span></p>
A few practical guidelines:
- Use
emunits: Sizes relative to the surrounding text keep emoji proportional in headings and body copy alike. - Set
line-height: 1on the emoji span: Some emoji fonts have large vertical metrics, and a stray emoji can push one line further from its neighbours than the others. - Adjust vertical alignment by eye: Emoji fonts don't all share a baseline. A small negative
vertical-alignoften makes them sit better with Latin text. - Don't fake bold or italic: Emoji have no weights or italics. Browsers may synthesise them, which can look odd, so keep emoji outside
strongandemwhere possible or reset them.
strong .emoji,
em .emoji {
font-weight: normal;
font-style: normal;
}
Emoji in Large Display Type
Bitmap emoji fonts store images at a set of fixed sizes, then scale them. At very large sizes, such as in a hero heading, bitmap emoji can look soft. If an emoji is a key visual element at a large size, an SVG image of it is often sharper and also gives you full control of the design.
Emoji Sequences and String Length
Many emoji are built from several code points that the font combines into one glyph:
- Skin tone modifiers: A thumbs-up followed by a skin tone character such as U+1F3FD.
- Zero width joiner sequences: Family, profession and many other emoji join simpler emoji with U+200D.
- Flags: Pairs of regional indicator letters, such as the letters G and B for the UK flag.
- Keycaps: A digit followed by U+FE0F and U+20E3.
To the reader, each is one character. To JavaScript, it's several. String length in JavaScript counts UTF-16 code units, and emoji outside the Basic Multilingual Plane take two units each.
const thumbs = "ππ½";
const family = "π¨βπ©βπ§";
console.log(thumbs.length);
console.log([...thumbs].length);
console.log(family.length);
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const count = (s) => [...segmenter.segment(s)].length;
console.log(count(thumbs));
console.log(count(family));
4
2
8
1
1
Intl.Segmenter counts user-perceived characters, known as grapheme clusters, which is what you want for character limits and truncation. It's supported in all current major browsers and in Node.js.
Truncating Safely
Cutting a string with slice can split an emoji sequence in half, leaving a broken glyph or a stray skin tone square. Truncate by grapheme instead:
function truncate(text, max, ellipsis = "β¦") {
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
const parts = [...segmenter.segment(text)].map((s) => s.segment);
return parts.length > max ? parts.slice(0, max).join("") + ellipsis : text;
}
console.log(truncate("Great job ππΎππΎππΎ", 11));
Great job ππΎβ¦
For purely visual truncation, CSS text-overflow: ellipsis is grapheme-aware and won't split sequences, because it works on rendered text.
Emoji and Accessibility
Screen readers announce emoji by their Unicode name, so a heart is read out as "red heart" and a row of five clapping emoji may be announced five times. That's helpful when an emoji carries meaning and tiresome when it's decoration.
- Never replace words with emoji: "Ships in 3 π" doesn't tell a screen reader user, or many sighted users, that you mean days of delivery.
- Hide decorative emoji: Wrap them in a span with
aria-hidden="true". - Label meaningful emoji: Use
role="img"and anaria-labelthat says what you mean, which may differ from the official name. - Avoid long runs: Repeated emoji are tedious to listen to and add little.
- Put emoji at the end, not the start, of links and headings: Users who navigate by headings or links hear the first words first.
<!-- Decorative -->
<h2>Release notes <span aria-hidden="true">β¨</span></h2>
<!-- Meaningful, with a label that matches the intent -->
<p>
Build status:
<span role="img" aria-label="passed">β
</span>
</p>
Emoji also aren't exempt from colour contrast. A pale yellow emoji used as the only status indicator on a white background can be hard to see, so pair it with text.
Emoji in Names, Headings and URLs
A few more places where emoji cause trouble:
- Page titles and meta descriptions: Search engines may show or drop emoji at their discretion, so don't depend on them to convey meaning in search results.
- URLs and slugs: Emoji in URLs are percent-encoded and hard to share or type. Strip them when generating slugs.
- Form fields: Users can type emoji into name and address fields. Make sure your database uses a full Unicode encoding, such as
utf8mb4in MySQL, rather than the older three-byteutf8, which can't store most emoji. - Brand names: If your product name includes an emoji, decide whether the emoji is part of the name. If it is, label it consistently; if not, hide it.
FAQ: Emoji in Web Typography
Each platform draws emoji with its own font, such as Apple Color Emoji on Apple devices, Segoe UI Emoji on Windows and Noto Color Emoji on Android. The character is the same, but each font's artwork is different. You can load one emoji font for everyone, but most sites let each device use its own.
At the end, after your text fonts and generic family. If an emoji font comes first, it may draw digits and some punctuation in its own style, because those characters also have emoji forms.
Add the text variation selector U+FE0E straight after the character, or use font-variant-emoji: text in browsers that support it. Platform support for text presentation varies, so test the specific symbols you use.
Yes. Screen readers announce emoji using their Unicode names, such as "face with tears of joy". Hide decorative emoji with aria-hidden and give meaningful ones a role of img with a clear aria-label.
JavaScript's length property counts UTF-16 code units, and many emoji use two units or are sequences of several code points. Use Intl.Segmenter with grapheme granularity to count characters the way a reader sees them.
Yes, sparingly. Size them in em units so they scale with the heading, place them at the end rather than the start, and hide them from screen readers if they are purely decorative.
Conclusion
Emoji are text, drawn by colour fonts that differ on every platform. Using them well starts with the font stack: keep your text fonts first and list the system emoji fonts at the end, load a font like Noto Color Emoji only when cross-platform consistency really matters, and use variation selectors or font-variant-emoji to choose between colour emoji and plain symbols.
From there, size emoji in em, tame their line height and alignment, and treat them carefully in code, counting and truncating by grapheme rather than by string length. Finally, remember that every emoji is read aloud to screen reader users. Use them to add tone, never to replace words, hide the decorative ones, and label the ones that carry meaning.


