font-display strategy
The font-display strategy on declared fonts: swap, block, optional, fallback, auto.
At a glance the headline numbers for font-display strategy
The font-display strategy on declared fonts: swap, block, optional, fallback, auto.
49.6% of fonts use font-display swap. 9.6% still hide their text while loading.
The font-display strategy mix who uses what, and how fast each group loads
font-display strategy. On the fleet: 49.6% swap, 39.7% auto, 9.6% block. 57.8% of sites use at least one swap.
Passing INP per bucket every category and count level at once - color is the pass rate
Each row is a category, each column its own count bucket (few on the left, many on the right); the cell is the share of those sites passing INP.
Block swings the hardest: 91% of sites pass INP with few, 84% with many. computed
Few vs many - does quantity cost INP? the pass rate with few vs many of each category
Per category: the pass rate among pages with FEW of it (hollow ring) against pages with MANY (solid dot), worst trend first. Thin buckets are excluded from the endpoints.
More Block costs the most: the INP pass rate falls from 91% with few to 84% with many. computed
Why this matters for the Core Web Vitals, and where to start fixing it
font-display decides what text does while the web font downloads. block hides it: invisible text, and a direct LCP delay when the LCP element is text. swap shows the fallback font immediately and switches when the web font lands, and that switch reflows the page (CLS) when the two fonts have different metrics. optional shows the fallback and only uses the web font if it arrives fast, which is the safest setting for the vitals.
The CLS half of swap is fixable without giving up the font: tune the fallback with size-adjust and the override descriptors so the swap barely moves anything.
Chrome field data from 189,915 sites, representing millions of real page loads. How we measured.
Live queries (2) — admin only
SELECT 1 AS ok FROM site_metric_bags LIMIT 0;
WITH flat AS (
SELECT b.cat, b.n, b.size, s.crux."phone".inp AS cwv_val
FROM site_metric_bags b
JOIN sites s ON s.origin = b.origin
WHERE b.path = 'fonts.display'
AND b.cat IS NOT NULL
),
totals AS (
SELECT cat,
SUM(n) AS fleet_n,
SUM(size) AS fleet_size,
COUNT(*) FILTER (WHERE n IS NOT NULL) AS sample,
COUNT(*) FILTER (WHERE n > 0) AS with_any,
quantile_disc(n, 0.95) AS p95,
COUNT(*) AS n_rows
FROM flat
GROUP BY cat
),
widths AS (
SELECT *,
GREATEST(1, CAST(round(COALESCE(p95, 0)) AS INTEGER)) AS cap,
LEAST(GREATEST(1, CAST(round(COALESCE(p95, 0)) AS INTEGER)), 12 - 1) AS regular_buckets
FROM totals
),
widths2 AS (
SELECT *,
GREATEST(1, CAST(ceil(cap * 1.0 / regular_buckets) AS INTEGER)) AS width
FROM widths
),
binned AS (
SELECT f.cat, f.cwv_val, w.fleet_n, w.fleet_size, w.sample, w.with_any, w.width, w.regular_buckets,
CASE WHEN f.n IS NULL THEN NULL
ELSE LEAST(CAST(FLOOR(f.n * 1.0 / w.width) AS INTEGER), w.regular_buckets)
END AS bucket_idx
FROM flat f
JOIN widths2 w ON f.cat = w.cat
)
SELECT cat, fleet_n, fleet_size, sample, with_any, width, regular_buckets,
bucket_idx,
COUNT(*) AS n,
quantile_disc(cwv_val, 0.5) AS median,
COUNT(*) FILTER (WHERE cwv_val IS NOT NULL) AS total_cwv,
COUNT(*) FILTER (WHERE cwv_val IS NOT NULL AND cwv_val <= 200) AS good
FROM binned
WHERE bucket_idx IS NOT NULL
GROUP BY cat, fleet_n, fleet_size, sample, with_any, width, regular_buckets, bucket_idx
ORDER BY cat, bucket_idx;