This doc set codifies the UI decisions, product patterns, intents, and behaviors proven in Speedway, the flagship app of the shared Versable design language. The ui-kit (packages/ui) is the vocabulary. Speedway's usage is the grammar. Both are captured here so any Versable app (walmart-mvp is the second consumer) can adopt the language without re-deriving it.
Audience: an agent (or person) orienting before any UI work in a Versable app.
Each doc has the same shape:
- §A Principles and mental models: the heuristics, the "how to decide," the why.
- §B Presets and how-to: the concrete patterns to reach for.
- §C Use cases through the app: exhaustive, grouped by surface, with file:line evidence.
The doc map#
| Doc | Trait | Layer |
|---|---|---|
| 01-foundations.md | color, type, spacing, radius, shadow, responsiveness | elementary |
| 02-buttons-and-actions.md | button variants, shades, icons, tooltips, wording | elementary |
| 03-status-language.md | badges, chips, banners, alerts, links | elementary |
| 04-loading-and-states.md | skeletons, spinners, pending bars, empty/error states | elementary |
| 05-cards-and-surfaces.md | cards, framed sections, stat tiles, spacing | composite |
| 06-tables.md | the data-table model: selection, peek, expansion, tints | composite |
| 07-toolbars-and-filters.md | toolbar presets, facets, filter chips, search | composite |
| 08-navigation-and-shell.md | sidebar, topbar, breadcrumbs, page title, shell | composition |
| 09-page-composition.md | widths, padding, section rhythm, page archetypes | composition |
| 10-overlays.md | peek panels, modals, dropdowns, toasts | composition |
| 11-motion.md | animations, transitions, entrance conventions | cross-cutting |
| 12-typography.md | the ramp, roles, title sets, the ink ladder, mono, tracking, truncation | cross-cutting |
Start from what you were asked for, not from a trait#
The table above is organised by trait, which is right for keeping each rule in exactly one place, and wrong for the moment an agent is handed a request. If you were asked to build something, start here instead. Each recipe carries a bundle checklist: every row is a thing the request brings along whether or not anyone said it, with its default, the rule that governs it, and when you may deviate.
Read the recipe's bundle BEFORE the trait docs. The bundle is what makes completeness checkable rather than inferred, and incomplete assembly is the most corrected failure in both apps.
| You were asked for | Recipe | Traits it pulls in |
|---|---|---|
| a list, table, queue, index, "show me all the X" | app-patterns/10-recipe-browsable-list.md | 06 07 04 05 09 02 |
| a record detail, preview, drill-in, "click a row and see it" | app-patterns/11-recipe-record-detail.md | 10 06 04 03 02 |
| a form, settings, or config surface | not yet written | 02 04 05 10 |
| a dashboard or stat overview | not yet written | 05 09 03 04 |
| an import or upload flow | not yet written | 02 04 10 |
| an admin or diagnostic surface | not yet written | 06 09 |
The unwritten rows are listed on purpose. An agent that finds no recipe for its request should know the gap is known rather than assume the request carries no bundle, and should fall back to the nearest written recipe plus the trait docs its row names.
The core mental models#
1. Three token layers. Only the middle one knows about themes.
Layer 0 is the kit palette: full OKLCH ramps (50 to 950) per hue family, "one ramp, not anchors with filler" (packages/ui/theme/colors.css:14-21). Layer 1 is the kit semantic slots (primary, success, paper, surface, ink, muted, hairline, field dials, status soft/line pairs). These are defined once per theme, in versable-light and versable-dark. Layer 2 is an app's bespoke aliases (Speedway's app.css:11-48): values that point into the kit, with names kept so rules recolor in one place. The hard-won rule: app tokens must read the semantic layer, never the palette layer. Only the semantic layer flips per theme (app.css:50-59 documents the bug that taught this).
2. Identity is not status.
Colors that say what a thing is (part-type, normalize, content, attribute, source, scrape, file, fitment identity tokens) are one vocabulary. Colors that say how it is doing (ok/warn/err/info/review) are another. "WHAT a thing is ... never how it's doing. A status colour can't say this" (colors.css:231-236). Never encode state in an identity color or vice versa.
3. The kit is generic. The app supplies meaning.
Kit components take explicit props (active booleans, NavGroup[], slot nodes) rather than inferring from context. Speedway centralizes meaning in single-source maps. PAGE_META is the canonical example: the sidebar, page titles, and tooltips all read the same entry, so icon and label can never drift (app/lib/page-meta.ts:5-8).
4. Feedback is local and layered, never global dimming.
A pending navigation shows a spinner on the clicked link (delayed 120ms) plus a topbar sweep bar (delayed 90ms). Content is never dimmed: "The sweep bar says 'working'; the route's skeleton says 'here is what is arriving'" (speedway app.css:1093-1102). This generalizes: loading feedback belongs to the thing that is loading.
5. Deliberate absence is a decision, not a gap. Speedway is full of dormant-not-deleted code with dated deprecation comments and revival instructions (workspace switcher, schemas nav, files pages). Owner rulings are recorded verbatim, in place, with dates. When these docs quote a ruling, the quote is binding. The pattern generalizes: record the ruling where the code is, and keep the alternative revivable.
6. Accessibility numbers are measured, not vibes.
Contrast decisions cite measurements: solid warning moved to amber-600 under white ink because "white on amber-500 reads 2.05:1" and the owner wanted white like success (colors.css:168-174). Dark-mode muted text is a 65% mix because it is "the first step clearing 4.5:1 at 12-14px" (speedway app.css:67-69). Copy that discipline, not just the values.
7. Make the wrong state unrepresentable before documenting the ban.
Where a misuse can be made impossible, the API change beats the Banned entry: PageSkeleton requires layout rather than defaulting to a guess about a page nobody looked at (packages/ui/src/page-skeleton/page-skeleton.types.ts:12), the split Button derives its caret's accessible name rather than asking every caller to remember one (packages/ui/src/button/button.tsx:264-265), and presets carry ruled combinations so a caller picks a name instead of reassembling the rule. A Banned entry is the fallback for what the API cannot reach, never the first tool.
Authority and provenance#
- Kit source:
packages/ui/srcandpackages/ui/themein this repo (versable-builder), currently at0.0.25. - Consumer pins differ, and the difference decides whether a rule in these docs is reachable. Walmart pins
^0.0.24(walmart-mvp/frontend/package.json:21), speedway pins^0.0.14(speedway/package.json:36). Under npm semver a caret on a0.0.xversion is a patch pin, so^0.0.14resolves to exactly0.0.14: everything added to the kit after that is unreachable in a clean speedway install. Speedway's dev tree hides this, because itsnode_modules/@versable-git/uiis a symlink topackages/uiand therefore runs the local0.0.25. So dev behaviour and a clean install disagree there. Any rule in these docs phrased "as of kit 0.0.N" must be checked against the consuming app's pin before you rely on it, and that especially covers02 §A9, whose claim that a kit Button is covered for accessibility depends on0.0.24. - Usage evidence: Speedway at
~/Code/Versable/speedway, as of its 2026-08-06 customer launch. - Owner rulings quoted in these docs are verbatim from code comments. The dated ones (2026-07-07 palette round, 2026-07-17 identity placement, 2026-07-21 doc-29 workspace hiding, 2026-07-29 crumb and counting rules, 2026-08-03 nav and theme gating, 2026-08-05 card-follows-tile) carry the most weight.
- Raw inventories behind these docs:
docs/evidence/20260807-0230-speedway-design-language/(five per-surface registers with file:line for every claim).