Files
ImBenjiandClaude Opus 5.5 b5e5ed5b75 Add a design system for Claude Design, generated from garage_ui
Tokens come out of garage_ui/tool/export_design_tokens_test.dart, the
components are CSS on top of them, and the cards cover foundations,
components and whole hub and Arcs & Angles screens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013F4NWNvYcdeSgqbWMT1VQ7
2026-09-23 23:19:02 +01:00
..

Garage design system

The look of every Garage app — the hub, the payment portal, Arcs & Angles — as tokens, CSS and preview cards, for designing new Garage screens.

It's generated from the Flutter package, not drawn separately: tokens.css and tokens.json come out of garage_ui/tool/export_design_tokens_test.dart, and components.css only uses those tokens. Don't edit the token files by hand — change garage_ui and rerun:

cd garage_ui && flutter test tool/export_design_tokens_test.dart
design_system/
  tokens.css, tokens.json   colours, density, type, shape — generated
  components.css            the components, built on the tokens
  foundations/              colour, type, spacing, shape, density
  components/               buttons, fields, selects, lists, dialogs, …
  patterns/                 whole screens from the hub and Arcs & Angles

The one rule: let the theme speak

The colour scheme, the density and the component's variant decide every colour, font size, weight, icon size, padding, radius and gap. Designing a Garage screen is choosing which component and which variant. Then leave it alone.

  • No custom colours, font sizes or weights on a component or its contents.
  • No icon sizes or colours — an icon takes them from where it sits.
  • No hand-picked padding, gaps or radii. Use the --gap-* and --radius-* tokens, and let components that already carry spacing keep it.
  • No home-made badges, pills, warning boxes or popovers built from a box with a fill and a border. If a component doesnt exist, that's a gap in the system, not an invitation.
  • No shadows. Everything is flat — surfaces are separated by a step in lightness and a hairline border.

If something looks wrong, the answer is a different variant or a different component.

Setting up a screen

<html data-scheme="carbon" data-density="product">
  <link rel="stylesheet" href="components.css">
  • Scheme: carbon (default — true black, no accent hue; what the Garage apps run), dark, light.
  • Density: product (default — web and product apps, 12px text, 33px controls), compact (dense desktop tools like Arcs & Angles, 10px text, 23px controls), normal (compact but roomier). Pick one per app. Don't mix.

Type is Geist, and Geist Mono for numbers and readouts. Body text is weight 300, control labels 400, primary buttons 600.

Layout

Every Garage screen is the same shape: a chrome ground, panels standing on it, and panel-gap gutters between them.

<div class="shell">
  <nav class="panel sunken">…rail…</nav>
  <main class="panel">…workspace…</main>
</div>

Headers and footers are chrome-bars. Only one panel is highlighted (panel active) at a time.

A hub page is a centred column inside the workspace panel: a page-heading, then either a stack of properties-sections or sub-heading + settings-list forms. See patterns/.

Choosing components

Buttons

Variant For
primary the one action on the view, or the thing that's on / chosen
secondary a neutral filled control; the selected nav item; a button welded to a field
outline a normal action that isn't the main one — Cancel, Retry
ghost chrome, toolbars, nav items, row actions
ghost-destructive remove / revoke / delete as a row action
destructive the confirm button of a destructive dialog
link, text inline in copy

Icon-only buttons are btn icon-only. A nav item is ghost with a leading icon, left-aligned, secondary when selected — never primary. Toolbar toggles are primary when on, outline when off.

Fields

  • Free text: the default outline field.
  • Values inside a properties section: field secondary. A read-only value is a read-only secondary field — never bare text in a value slot.
  • Selects: secondary in a properties section, ghost in a settings list, outline elsewhere.
  • Numbers in an editor: secondary, centred, unit as a muted suffix.

Lists

  • properties-section — a boxed card of label/value rows. Inspectors, account pages, one card per record in a list. Controls inside are secondary.
  • settings-list — unboxed rows ruled between entries, for forms. Controls inside are ghost; text fields are outline.

Subtitles are short static descriptions of what something is. Never live data — no counts, dates, names, emails or statuses. Facts go in rows.

Dialogs are dialog with the actions in their band: outline Cancel, then a primary or destructive confirm. Max width is fixed; don't widen it.

Loading is a spinner. No skeletons. Empty is one muted line. Error is the message, muted, and an outline Retry.