Emoji
An emoji is a character, and browsers pick the font for it inconsistently — sometimes
a colour font, sometimes a monochrome fallback, sometimes the body font's own glyph.
.emoji names the colour stack explicitly and fixes the size and baseline,
so the character sits with its text rather than above or below it.
When to use it
Wrap an emoji in a <span class="emoji"> whenever it appears beside
text you care about. Without it the character inherits the body font, which on some
platforms produces a black-and-white outline and on others shifts the line height of
the whole paragraph.
Deployed 🚀 in 42 seconds
Nightly export finished ✅ with 2,481 rows
<p>Deployed <span class="emoji">🚀</span> in 42 seconds</p>
<p>Nightly export finished <span class="emoji">✅</span> with 2,481 rows</p>
<p>Deployed <span class="emoji">🚀</span> in 42 seconds</p>
<p>Nightly export finished <span class="emoji">✅</span> with 2,481 rows</p>
font-style: normal and font-weight: 400 are set as well, so
an emoji inside a <strong> or an <em> is not
asked to synthesise a bold or italic it does not have.
Sizes
Three steps, each pairing a size with the vertical-align that keeps it on
the baseline — the alignment is the part that has to change with the size, and getting
it wrong is what makes a large emoji look like it is falling off the line.
<span class="emoji">🎉</span>
<span class="emoji emoji-lg">🎉</span>
<span class="emoji emoji-xl">🎉</span>
<span class="emoji emoji-hero">🎉</span>
<span class="emoji">🎉</span>
<span class="emoji emoji-lg">🎉</span>
<span class="emoji emoji-xl">🎉</span>
<span class="emoji emoji-hero">🎉</span>
.emoji-hero is the odd one: it is sized in rem rather than
em, because at that scale it is a graphic in its own right rather than
something sitting beside text.
Tiles
.emoji-tile puts the character in a tinted rounded square — the emoji
counterpart of .icon-tile, for an empty
state or a feature row. .emoji-tile-round makes it a circle.
<span class="emoji-tile"><span class="emoji emoji-lg">📦</span></span>
<span class="emoji-tile"><span class="emoji emoji-lg">🔔</span></span>
<span class="emoji-tile emoji-tile-round"><span class="emoji emoji-lg">🎯</span></span>
<span class="emoji-tile"><span class="emoji emoji-lg">📦</span></span>
<span class="emoji-tile"><span class="emoji emoji-lg">🔔</span></span>
<span class="emoji-tile emoji-tile-round"><span class="emoji emoji-lg">🎯</span></span>
Picker grid
.emoji-grid is an auto-fitting grid of buttons that scale slightly on
hover — a reaction picker or a quick-select. Deck styles the grid and supplies no
picker: the characters, the categories and the search are yours.
<div class="emoji-grid" style="max-inline-size:20rem">
<button type="button" aria-label="Thumbs up"><span class="emoji emoji-lg">👍</span></button>
<button type="button" aria-label="Celebrate"><span class="emoji emoji-lg">🎉</span></button>
<button type="button" aria-label="Eyes"><span class="emoji emoji-lg">👀</span></button>
<button type="button" aria-label="Rocket"><span class="emoji emoji-lg">🚀</span></button>
<button type="button" aria-label="Heart"><span class="emoji emoji-lg">❤️</span></button>
<button type="button" aria-label="Thinking"><span class="emoji emoji-lg">🤔</span></button>
</div>
<div class="emoji-grid" style="max-inline-size:20rem">
<button type="button" aria-label="Thumbs up"><span class="emoji emoji-lg">👍</span></button>
<button type="button" aria-label="Celebrate"><span class="emoji emoji-lg">🎉</span></button>
<button type="button" aria-label="Eyes"><span class="emoji emoji-lg">👀</span></button>
<button type="button" aria-label="Rocket"><span class="emoji emoji-lg">🚀</span></button>
<button type="button" aria-label="Heart"><span class="emoji emoji-lg">❤️</span></button>
<button type="button" aria-label="Thinking"><span class="emoji emoji-lg">🤔</span></button>
</div>
Classes
Generated from src/08-mobile.css by tools/docs/extract.mjs.
| Class | Layer | What it does | Source |
|---|---|---|---|
.emoji |
deck.mobile |
Pins the emoji font stack and metrics so an emoji renders at the same size and baseline on Windows, iOS and Android instead of jumping between them. | 08-mobile.css:183 |
.emoji-grid |
deck.mobile |
Emoji picker grid — drop your own set in | 08-mobile.css:214 |
.emoji-hero |
deck.mobile |
Sets font-size, line-height. | 08-mobile.css:197 |
.emoji-lg |
deck.mobile |
Sets font-size, vertical-align. | 08-mobile.css:195 |
.emoji-tile |
deck.mobile |
Emoji as an avatar or a status marker | 08-mobile.css:200 |
.emoji-tile-round |
deck.mobile |
Sets border-radius. | 08-mobile.css:211 |
.emoji-xl |
deck.mobile |
Sets font-size, vertical-align. | 08-mobile.css:196 |
Tokens
| Token | Live | Declared value |
|---|---|---|
--surface-2 |
light-dark(var(--ink-25), var(--ink-900)) |
|
--surface-hover |
light-dark(var(--ink-50), var(--ink-900)) |
|
--r-md |
12px |
|
--r-full |
999px |
|
--space-2 |
.5rem |
Accessibility
- An emoji is read aloud, and the reading is often long. "🎉" is announced as "party popper". In a sentence that is usually fine; in a row of six it is noise.
-
Decorative emoji should be hidden.
aria-hidden="true"on the span when the character is ornament beside text that already says everything. - An emoji carrying meaning needs words too. A ✅ standing alone in a table cell is announced as "check mark button", which is a description of a picture rather than a status. Put the status in text and hide the emoji.
-
Picker buttons need real labels. A grid of unlabelled emoji
buttons is announced as a list of glyph names with no indication of what pressing
one does. Every button in the example above carries
aria-label. -
Emoji rendering is not universal. The font stack lists five
families and falls back to
sans-serif; on a system with none of them a character may render as a box. Never let an emoji be the only carrier of meaning. - Skin-tone and ZWJ sequences vary by platform. A family emoji may render as one glyph or as four separate ones depending on the font, which changes both the width and what is announced.
Right to left
Emoji are neutral characters in the bidirectional algorithm, so they take the direction of the surrounding text and need nothing from Deck. The one thing worth knowing is that a directional emoji — an arrow, a pointing hand — does not mirror, unlike Deck's directional icons. If direction matters, use an icon.
تم النشر 🚀 خلال ٤٢ ثانية
<p dir="rtl">تم النشر <span class="emoji">🚀</span> خلال ٤٢ ثانية</p>
<p dir="rtl">تم النشر <span class="emoji">🚀</span> خلال ٤٢ ثانية</p>
Reduced motion
Only the picker grid moves — its buttons scale to 1.16 on hover — and the global reset
collapses that transition to .01ms under
prefers-reduced-motion: reduce. Nothing else animates.
Printing
Emoji print as characters, in colour, on a colour printer. There is no print rule and none is needed — though a colour emoji printed in greyscale can become an indistinguishable dark blob, which is the same argument for not letting one carry meaning alone.
Overriding it
@layer app.components {
/* Prefer a bundled emoji font you ship yourself */
.emoji { font-family: "Noto Color Emoji", sans-serif; }
/* A tighter picker */
.emoji-grid { grid-template-columns: repeat(auto-fit, minmax(2rem, 1fr)); }
}
Shipping your own emoji font is the only way to make rendering identical everywhere. It is also several megabytes, which is why Deck names a stack instead.
When not to use it
-
Not instead of an icon. An icon takes
currentColor, mirrors under RTL, prints as line art and looks the same on every platform. An emoji does none of those. - Not for status. ✅ and ❌ are pictures whose announced names describe the picture, not the state.
- Not in a heading you will translate. Emoji meaning is cultural, and a gesture that is friendly in one locale is not in another.
- Not as a bullet. A list marked with emoji is announced with the glyph name before every item.
- Not in a value you store and compare. The same emoji has multiple byte sequences depending on variation selectors, so equality checks surprise people.