Current Behavior
img.emoji (the inline Twemoji <img> rendered by the formatter) is styled in extensions/emoji/less/forum.less with a height but no width and no aspect-ratio:
img.emoji {
height: 1.5em;
margin: 0 .1em;
vertical-align: -0.3em;
}
The emoji SVGs are loaded from the jsDelivr CDN (cdn.jsdelivr.net/gh/twitter/twemoji@latest/...) and the rendered <img> carries no intrinsic dimensions either. With height: 1.5em but width: auto, the browser cannot compute the box width until the SVG has downloaded, so it reserves zero width for each emoji and reflows the surrounding text once each SVG arrives.
On posts containing several emoji this produces a visible Cumulative Layout Shift (CLS), especially on first paint / cold CDN cache / throttled mobile connections. On my forum (~55k posts, many emoji-heavy) this was a measurable contributor to Search Console "poor" CLS on mobile URLs.
Steps to Reproduce
- Post a message containing several emoji (e.g.
😀🎹🔥👍🎉).
- Load the discussion with the Twemoji SVGs uncached (hard refresh with cache disabled, or DevTools network throttling).
- Watch the text reflow horizontally as each emoji SVG finishes loading.
Reproducible on a clean v2.0.0-rc.2 install.
Expected Behavior
Because every Twemoji glyph is square (1:1), the browser can reserve the correct box before the SVG loads if the aspect ratio is declared in CSS. No layout shift.
Suggested Fix
A one-line CSS addition in extensions/emoji/less/forum.less — no change to the generated formatter bundle required, and it survives bundle regeneration:
img.emoji {
height: 1.5em;
aspect-ratio: 1; /* reserve a square box while the SVG loads → no CLS */
margin: 0 .1em;
vertical-align: -0.3em;
}
With height: 1.5em already set, aspect-ratio: 1 lets the browser derive width: 1.5em immediately. (width: 1.5em is an equivalent explicit alternative.) This is deployment-agnostic — all Twemoji are square, so it's correct for every install.
Environment
- Flarum:
v2.0.0-rc.2
- Extension:
flarum/emoji (bundled)
- PHP 8.4, observed across Chromium and WebKit.
Additional Context
I've been running the equivalent fix locally (as width="20" height="20" attributes injected into the formatter's emoji <img> output), which confirms it eliminates the shift — but the CSS aspect-ratio approach above is cleaner since it lives in the extension's own stylesheet rather than the generated bundle. Happy to open a PR if the fix looks good.
Current Behavior
img.emoji(the inline Twemoji<img>rendered by the formatter) is styled inextensions/emoji/less/forum.lesswith a height but no width and noaspect-ratio:The emoji SVGs are loaded from the jsDelivr CDN (
cdn.jsdelivr.net/gh/twitter/twemoji@latest/...) and the rendered<img>carries no intrinsic dimensions either. Withheight: 1.5embutwidth: auto, the browser cannot compute the box width until the SVG has downloaded, so it reserves zero width for each emoji and reflows the surrounding text once each SVG arrives.On posts containing several emoji this produces a visible Cumulative Layout Shift (CLS), especially on first paint / cold CDN cache / throttled mobile connections. On my forum (~55k posts, many emoji-heavy) this was a measurable contributor to Search Console "poor" CLS on mobile URLs.
Steps to Reproduce
😀🎹🔥👍🎉).Reproducible on a clean v2.0.0-rc.2 install.
Expected Behavior
Because every Twemoji glyph is square (1:1), the browser can reserve the correct box before the SVG loads if the aspect ratio is declared in CSS. No layout shift.
Suggested Fix
A one-line CSS addition in
extensions/emoji/less/forum.less— no change to the generated formatter bundle required, and it survives bundle regeneration:With
height: 1.5emalready set,aspect-ratio: 1lets the browser derivewidth: 1.5emimmediately. (width: 1.5emis an equivalent explicit alternative.) This is deployment-agnostic — all Twemoji are square, so it's correct for every install.Environment
v2.0.0-rc.2flarum/emoji(bundled)Additional Context
I've been running the equivalent fix locally (as
width="20" height="20"attributes injected into the formatter's emoji<img>output), which confirms it eliminates the shift — but the CSSaspect-ratioapproach above is cleaner since it lives in the extension's own stylesheet rather than the generated bundle. Happy to open a PR if the fix looks good.