Workshop Kit · widget reference
Shippedfont-pair-picker
Six pre-vetted heading + body type pairings, presented as preview cards rendering the same restaurant name + tagline. Every pair is a CSS font-family stack built on system + commonly-installed fallbacks — no remote font fetch — so the rail's sandbox="" iframe shows the actual choice without contradicting the suite's "no fetch" posture. Real Google Fonts come through the L14 generator's @import block at deploy time, not here. Writes fontPair: { id, heading, body } to MuntinContext.
Live demo
Six cards, one selected at any moment. Click or use arrow keys to move the focused card; Enter or Space commits the selection. The chosen pair's id, heading stack, and body stack write to MuntinContext.fontPair.
No config block — six pairs are baked into the widget
The six pairs
The roster is curated for restaurant use — each pair maps to a recognizable kind of operation. Operators don't have to know typography terminology to pick well; the blurbs tell them what each pair "reads as."
| id | Heading stack | Body stack | Reads as |
|---|---|---|---|
editorial-modern |
Fraunces / Playfair / Georgia / serif | Inter / system-ui sans | The Method default; the bootcamp itself is set this way. |
diner-classic |
Bebas / Oswald / Impact / Arial Narrow | Georgia / serif | Breakfast spots, classic American counters, diner-sign feel. |
trattoria |
Playfair / Cormorant / Garamond / serif | Lora / EB Garamond / serif | Italian-restaurant default; established without trying too hard. |
taqueria |
Anton / Bebas / Impact / sans | Inter / system-ui sans | Reads street, reads loud — hand-painted-sign style restaurants. |
minimal-tasting |
Inter / system-ui sans | Inter / system-ui sans | Tasting menus, wine bars — where restraint IS the brand. |
corner-store |
Caveat / Kalam / Comic Sans MS / cursive | Inter / system-ui sans | Cafés, bakeries, juice bars — "the person behind the counter." |
Contract
No data attributes beyond data-widget; no inline JSON config. The widget reads the previously-selected pair id (if any) from MuntinContext.fontPair.id and pre-selects that card.
| Behavior | Notes |
|---|---|
Reads fontPair.id |
Pre-selects the matching card on mount. Falls back to no selection if absent (the first card receives keyboard tab focus, but aria-checked="false" until the operator commits). |
Writes fontPair |
{ id, heading, body } — the id is the stable slug; heading + body are the CSS family stacks the L14 generator + rail use directly. |
| No remote font loads | Every pair uses system + commonly-installed fallbacks so the rail's sandboxed iframe shows the correct rendering without violating "no fetch." The L14 generator pairs the chosen id with a Google Fonts @import at deploy time. |
| Bilingual labels | Each pair has name_en / name_es and blurb_en / blurb_es. The widget reads deps.locale at mount and renders the matching strings. The sample restaurant name + tagline are also localized ("Jolene's Cafe" → "Café Jolene"). |
| Cross-device sync | fontPair is on CONFIG_ALLOWED_KEYS in src/lib/course.js and on the matching ALLOWED array in assets/js/course-config-sync.js, so signed-in operators see the same choice on their second device. |
Markup
That's it. The six cards render automatically and the selection writes to fontPair on every commit.
Accessibility
- The card container is
role="radiogroup"witharia-labelledbypointing at the visible heading. - Each card is
role="radio"witharia-checked. The selected card is the only one withtabindex="0"; others aretabindex="-1", per the WAI-ARIA radio-group pattern. - Keyboard: Arrow keys (Up/Down/Left/Right) move + commit the selection; Home and End jump to first/last; Enter and Space commit the focused card.
- A polite live region announces the selection: "Editorial modern font pair selected." Screen-reader users hear the result without having to parse the visual preview.
- Focus is visible via a high-contrast outline distinct from the selected-state border.
Why six, not "build your own"
Typography is the place where operators get stuck or get bad. Six pre-vetted pairs reframes the choice from "make a typography decision" to "pick the one that reads like my place," which is the question they can actually answer. The blurbs ("reads street, reads loud") name the vibe in operator-language. L7 also includes the voice slider for tone — together they cover palette + voice + typography, the three brand-defining decisions the bootcamp needs locked before L8.
Where it ships
The L14 generator reads fontPair.id and emits a Google Fonts @import + CSS variables for --font-display + --font-body in the generated styles.css. The rail uses the same id to render its sandboxed iframe with system fallbacks while the operator is still picking — no deploy needed to preview.
Source
/tools/_shared/workshop/font-pair-picker.js — ~210 LOC. Exports { tag, contextKeys, mount, serialize } per the Workshop Kit widget contract. The six pairs live as a frozen PAIRS constant at the top of the file; the widget is otherwise a thin radio-group renderer with arrow-key navigation and a polite live-region announcer.