Boost Emoji Picker Performance with CSS content-visibility
The article explains how using the new CSS content-visibility property can dramatically reduce layout and paint costs when rendering tens of thousands of custom emojis, offering a lightweight alternative to full virtualization while preserving accessibility and searchability.
This article, translated from "Improving rendering performance with CSS content-visibility," describes a performance issue encountered with the emoji-picker-element on a Fediverse instance containing nearly 20,000 custom emojis, where opening the picker caused a noticeable freeze.
Each custom emoji is represented by a
<button>and an
<img>, resulting in about 40,000 DOM elements. Because no virtualization is used, all elements are inserted into the DOM, leading to slow rendering—Lighthouse recommends staying under 1,400 elements.
Although the images use
<img loading="lazy">to defer downloading, rendering 40k elements remains costly. The author initially considered virtualizing the list but avoided it due to complexity, perceived unnecessary need, and potential accessibility impact.
Instead, the author explored CSS
content-visibility, a new feature that allows parts of the DOM to be hidden from layout and paint without removing them from the accessibility tree, thus preserving searchability and ARIA semantics.
By treating each emoji category (e.g., "blobs", "cats") as a unit, the author applied
content-visibility: autoand
contain-intrinsic-sizeusing CSS custom properties to reserve space for off‑screen categories, preventing layout jumps during scrolling.
The CSS snippet used for each category is:
<code>.category {
content-visibility: auto;
contain-intrinsic-size:
/* width */ calc(var(--num-columns) * var(--total-emoji-size))
/* height */ calc(var(--num-rows) * var(--total-emoji-size));
}</code>Performance tests showed a modest 15% improvement in Chrome and 5% in Firefox for initial load, though still far behind full virtualization. Further gains were achieved by replacing
<img>elements with CSS background images via a ::after pseudo‑element on the
<button>, reducing the DOM element count from 40k to 20k.
Using IntersectionObserver to add an
.onscreenclass when a category scrolls into view, the author achieved roughly 45% faster rendering in both Chrome and Firefox, cutting load time from ~3 seconds to ~1.3 seconds. Safari's limited support for
content-visibility:auto-state-changerequired falling back to IntersectionObserver.
While this approach does not match the speed of a fully virtualized list and may not scale indefinitely, it offers a low‑cost implementation that maintains accessibility and searchability, making
content-visibilitya useful tool when ultimate performance is not the sole priority.
KooFE Frontend Team
Follow the latest frontend updates
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.