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
This commit is contained in:
ImBenji
2026-09-23 23:19:02 +01:00
co-authored by Claude Opus 5.5
parent 572c09039f
commit b5e5ed5b75
25 changed files with 3381 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
# 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:
```sh
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
<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, `panel`s standing on
it, and `panel-gap` gutters between them.
```html
<div class="shell">
<nav class="panel sunken">…rail…</nav>
<main class="panel">…workspace…</main>
</div>
```
Headers and footers are `chrome-bar`s. 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-section`s 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.