# 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 ``` - **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
…workspace…
``` 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.