Font Page Preview
A mock browser page that shows what each font-loading strategy actually looks like to the user. Pick block (FOIT), swap (FOUT), optional (FOFT), or no-fallback, and the preview walks a sample heading and paragraph through loading → swap → loaded, switching between invisible / fallback / loaded faces at each step. Drives the lesson where the swap-vs-no-swap difference is the punchline.
Page preview
Pick a strategy and watch the mock page render through the loading timeline.
Performance Matters
A 200KB font that arrives 600ms late will push every paragraph down two pixels — small enough to ignore, large enough to fail CLS.
- loadingfallback
- swaploaded
- loadedloaded
Installation
npx shadcn@latest add https://craftbits.dev/r/font-page-preview.jsonUsage
import { FontPagePreview } from "@craft-bits/core";
<FontPagePreview
title="Page preview"
pageHeading="Performance Matters"
pageBody="A 200KB font that arrives 600ms late will push every paragraph down two pixels."
loadDelayMs={1500}
/>Controlled mode — drive the strategy and the simulation state from a lesson scrubber:
const [strategy, setStrategy] = useState<FontPagePreviewStrategy>("swap");
const [state, setState] = useState<FontPagePreviewState>("loading");
<FontPagePreview
strategy={strategy}
onStrategyChange={setStrategy}
state={state}
onStateChange={setState}
/>Anatomy
- Header. Optional
titleanddescription. Both omittable for a chromeless variant. - Strategy picker. A
<fieldset>wrapping aradiogroupof four chips, one per strategy. Selecting one resets the simulation and replays fromloading. - Mock page. A tiny browser frame — three dots plus a status tag in the nav — wrapping a heading and a paragraph that re-render under the active strategy + state.
- Phase strip. Three cells (
loading,swap,loaded) showing the rendered phase at each step for the active strategy. The active cell is highlighted.
Understanding the component
- Strategy maps state to phase. Each strategy maps
(state)to one of three phases:invisible,fallback,loaded.blockandno-fallbackare invisible while loading;swapandoptionalshow the fallback. Onswap,block/swap/no-fallbackreveal the loaded face;optionalkeeps the fallback. Onloaded, onlyoptionalstays on the fallback for this view. - Simulation loop. Without a controlled
state, the component runs arequestAnimationFrameloop that advancesloading→swap→loadedagainst the configuredloadDelayMs. Picking a new strategy replays the timeline from the start. - Controlled state. When the
stateprop is provided, the RAF loop is disabled and the preview renders the given state directly. Useful for docs scrubbers, step-driven lesson explainers, and the customize panel above. - Reduced motion. Under
prefers-reduced-motion, the simulation loop is skipped — the preview snaps straight toloaded. The mock page still re-renders when the strategy changes; only the timed animation is suppressed.
Variants
// Slow connection — pushes the swap further out so the loading phase is visible.
<FontPagePreview loadDelayMs={3000} defaultStrategy="block" />
// Chromeless — drop the strategy picker and the phase strip for an inline visual.
<FontPagePreview hideStrategyPicker hidePhaseStrip />
// Controlled state — fix the timeline at the swap moment.
<FontPagePreview strategy="swap" state="swap" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | 'Page preview' | Optional heading above the mock page. |
description | ReactNode | — | Optional sub-headline under the title. |
strategy | FontPagePreviewStrategy | — | Controlled active strategy. |
defaultStrategy | FontPagePreviewStrategy | 'swap' | Initial strategy in uncontrolled mode. |
onStrategyChange | (s: FontPagePreviewStrategy) => void | — | Fires when the user picks a new strategy. |
state | FontPagePreviewState | — | Controlled simulation state. Disables the internal RAF loop. |
onStateChange | (s: FontPagePreviewState) => void | — | Fires when the simulation crosses into a new state. |
pageHeading | string | 'Performance Matters' | Heading rendered inside the mock page. |
pageBody | string | pangram-style | Body paragraph rendered inside the mock page. |
loadDelayMs | number | 1500 | Simulated network load delay in milliseconds. |
hideStrategyPicker | boolean | false | Hide the chip strip. |
hidePhaseStrip | boolean | false | Hide the bottom phase strip. |
headingAs | 'h2' | 'h3' | 'h4' | 'h3' | Tag for the title element. |
className | string | — | Merged onto the root via cn(). |
FontPagePreviewStrategy is the union 'block' | 'swap' | 'optional' | 'no-fallback'. FontPagePreviewState is 'loading' | 'swap' | 'loaded'.
Accessibility
- The wrapper is a
<section>withdata-cb-edu="font-page-preview", plusdata-strategy,data-state, anddata-phaseso consumers can extend tone-specific styling without monkey-patching CSS. - The mock page 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 mock page ("Web font rendered." / "Fallback font rendered." / "Text is invisible while the font loads.") conveys the phase without depending on font weight or italic styling.
- Under
prefers-reduced-motion, the simulation loop is skipped — the preview snaps toloadedand re-renders on strategy change without timed animation.
Credits
- Extracted from:
terminal-dreams(src/components/frontend-design/perf-other-assets/ui/FontPagePreview.tsx). The original pulledfontFrame,fontStrategy, andsetFontStrategyoff anassets-perf-context, rendered a fixed CLS gauge, and exposed fivefont-displayvalues keyed by a lesson-specificFontStrategyengine. This rewrite drops the context dependency, drops the CSS-Module classnames in favour of thecb-*tokens, and reframes the four behaviours around the FOIT / FOUT / FOFT / no-fallback teaching arc — consumers pick a strategy and the preview walksloading→swap→loadedon its own (or under a controlledstateprop).