Font Widget
A teaching widget for the four CSS font-display strategies — block, swap, fallback, optional. The widget renders a sample string and walks it through the FOIT, FOUT, and swap phases each strategy produces, against a simulated network load delay you can tune live. A metric strip underneath reports the resulting FOIT duration, swap behaviour, and CLS impact.
Preview
Font loading
Pick a strategy and watch the text walk through the phases.
The quick brown fox jumps over the lazy dog.
- FOITNone
- CLS impactAlways shifts
- Load delay1500ms
Customize
Simulation
swap
1500ms
Display
Installation
npx shadcn@latest add https://craftbits.dev/r/font-widget.jsonUsage
import { FontWidget } from "@craft-bits/core";
<FontWidget
title="Font loading"
sampleText="The quick brown fox jumps over the lazy dog."
loadDelayMs={1500}
/>Controlled mode — drive the strategy from outside, e.g. from a lesson stepper:
const [strategy, setStrategy] = useState<FontWidgetStrategy>("swap");
<FontWidget strategy={strategy} onStrategyChange={setStrategy} />Anatomy
- Header. Optional
titleanddescription. Both omittable for a chromeless variant. - Preview pane. Renders the sample string in either the fallback font or the (notionally) loaded web font, depending on the current phase. The pane is
aria-live="polite"so screen readers announce the phase change. - Strategy picker. A
<fieldset>wrapping aradiogroupof four chips, one perfont-displayvalue. Selecting one resets the simulation. - Metric strip. Three cells — FOIT, CLS impact, and the configured load delay — toned by the strategy's verdict.
Understanding the component
- Phase model. The widget walks through four phases:
invisible(FOIT),fallback(FOUT showing the system font),loaded(the web font swapped in), andfallback-final(the system font kept for the rest of the view). Each strategy maps the simulated tick count to one of these phases. - Simulated load delay.
loadDelayMsis how long the widget pretends the network takes to deliver the font. The picker resets arequestAnimationFrameloop and walks an internaltickMsfrom 0 toloadDelayMs + 200, then settles. - Reduced motion. Under
prefers-reduced-motion, the loop is skipped entirely — the widget snaps straight to the steady-state phase for the chosen strategy. - Verdict. Each strategy carries a static verdict (
good/neutral/bad) used to tone the metric row.blockisbadbecause of FOIT;swapisneutralbecause of CLS;fallbackandoptionalaregood.
Variants
// Slow connection — load delay past 3s pushes fallback into fallback-final.
<FontWidget loadDelayMs={3200} defaultStrategy="fallback" />
// Embedded inline — hide the metric strip when used inside prose.
<FontWidget hideMetrics />Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | 'Font loading' | Optional heading above the preview pane. |
description | ReactNode | — | Optional sub-headline under the title. |
strategy | FontWidgetStrategy | — | Controlled active strategy. |
defaultStrategy | FontWidgetStrategy | 'swap' | Initial strategy in uncontrolled mode. |
onStrategyChange | (s: FontWidgetStrategy) => void | — | Fires when the user picks a new strategy. |
sampleText | string | pangram | Sample string rendered in the preview pane. |
loadDelayMs | number | 1500 | Simulated network load delay in milliseconds. |
hideMetrics | boolean | false | Hide the bottom metric strip. |
headingAs | 'h2' | 'h3' | 'h4' | 'h3' | Tag for the title element. |
className | string | — | Merged onto the root via cn(). |
FontWidgetStrategy is the union 'block' | 'swap' | 'fallback' | 'optional'.
Accessibility
- The wrapper is a
<section>withdata-cb-edu="font-widget", plusdata-strategyanddata-phaseso consumers can extend tone-specific styling without monkey-patching CSS. - The preview pane is
aria-live="polite"andaria-atomic="true"— phase changes announce as one sentence rather than piecemeal updates. - The strategy picker is a real
<fieldset>+role="radiogroup"ofrole="radio"buttons, each witharia-checked. Buttons use native button semantics for keyboard activation; the radios usedata-statefor styling. - A visually-hidden sentence inside the preview pane ("Web font rendered." vs "Fallback font rendered.") conveys the visible/invisible flip without depending on font weight.
- Under
prefers-reduced-motion, the simulation loop is skipped — the widget snaps to the steady-state phase without animating.
Credits
- Extracted from:
terminal-dreams(src/components/frontend-design/sdp-web-performance/ui/FontWidget.tsx). The original pulledenabledOptimizations,activeProfile, andoptParamsoff aPerfContextand rendered a fixed before/after pipeline timeline. This rewrite drops the context dependency and the binary on/off framing — consumers pick any of the four strategies and tune the simulated load delay directly. The CSS-Module classnames are gone; the widget now consumes our token preset.