Skip to content

Commit f4e3cef

Browse files
authored
refactor(admin): add fluid spacing token scale (CoreBunch#87)
* refactor(admin): add fluid typography token scale * refactor(admin): add fluid spacing token scale
1 parent 0a61a31 commit f4e3cef

202 files changed

Lines changed: 1916 additions & 1590 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/design.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ The design is a **two-layer color model**: an achromatic base (surfaces, borders
1313
- **Bordered transparent inputs.** Inputs have a 1px white-alpha border, transparent background, and a pill 1em radius. Focus adds an inset achromatic glow.
1414
- **Floating overlay panels.** Spotlight, popovers, and modals use direct globals: `--bg-surface`, `--overlay-10`, `--panel-radius`, `--panel-blur`, and `--shadow-panel`.
1515
- **Editor controls** (toolbar buttons, chips) use `--radius` (6px) for default and `--radius-sm` (3px) for tight badges.
16-
- **One source of truth: `src/styles/globals.css`.** No hardcoded hex / rgb / hsl in admin / ui CSS modules — gated by `css-token-policy.test.ts`. Admin font sizes use the fluid `--text-*` scale — gated by `admin-typography-token-policy.test.ts`.
16+
- **One source of truth: `src/styles/globals.css`.** No hardcoded hex / rgb / hsl in admin / ui CSS modules — gated by `css-token-policy.test.ts`. Admin font sizes use the fluid `--text-*` scale, and admin spacing uses the fluid `--space-*` scale — gated by `admin-typography-token-policy.test.ts` and `admin-spacing-token-policy.test.ts`.
1717
- **CSS Modules only.** No Tailwind utility classes — gated by `noTailwindUtilities.test.ts`. No Tailwind ecosystem deps — gated by `no-tailwind-deps.test.ts`.
1818
- **Every interactive control goes through a UI primitive** from `src/ui/components/`. Bare `<button>` is gated.
1919
- **Icons come from `pixel-art-icons`.** Deep-imported for tree-shaking. No `lucide-react`, no inline SVG strings.
@@ -122,7 +122,7 @@ Animations are short, purposeful, and never block content. The `@media (prefers-
122122

123123
## Tokens
124124

125-
All tokens live in `src/styles/globals.css`. Anywhere you need a color, radius, shadow, font, or z-index, use a token. If the right token doesn't exist, **add one to `globals.css`** — never inline a value.
125+
All tokens live in `src/styles/globals.css`. Anywhere you need a color, radius, shadow, font, spacing value, or z-index, use a token. If the right token doesn't exist, **add one to `globals.css`** — never inline a value.
126126

127127
### Color tokens
128128

@@ -144,6 +144,13 @@ Fluid admin typography scale:
144144
--text-l, --text-xl, --text-2xl, --text-3xl, --text-4xl,
145145
--text-5xl, --text-6xl, --text-7xl
146146
147+
Fluid admin spacing scale:
148+
--space-px, --space-4xs, --space-3xs, --space-2xs,
149+
--space-xs, --space-s, --space-m, --space-l, --space-xl,
150+
--space-2xl, --space-3xl, --space-4xl, --space-5xl,
151+
--space-6xl, --space-7xl, --space-8xl, --space-9xl,
152+
--space-10xl, --space-11xl, --space-12xl
153+
147154
Overlay scale (white alpha):
148155
--overlay, --overlay-5, --overlay-10, --overlay-20, --overlay-30,
149156
--overlay-40, --overlay-50, --overlay-60, --overlay-70, --overlay-80,
@@ -210,6 +217,12 @@ Admin UI font sizes use a Core Framework-style fluid scale: `--text-3xs` through
210217

211218
These are admin tokens. The published-site Framework engine also emits short text-size tokens such as `--text-s`; that is a separate scope. Editor chrome injected into the canvas iframe maps admin sizes to `--chrome-text-*` before using them so it does not overwrite the site's Framework typography.
212219

220+
### Spacing tokens — fluid size scale
221+
222+
Admin UI spacing uses a Core Framework-style fluid scale: `--space-px`, then `--space-4xs` through `--space-12xl`. CSS Modules in `src/admin/` and `src/ui/` should use the scale for `margin`, `padding`, `gap`, `row-gap`, `column-gap`, and CSS-authored SVG dimensions, never a hardcoded pixel value. `--space-px` stays fixed for true 1px hairline gaps.
223+
224+
These are admin tokens. The published-site Framework engine also emits short spacing tokens such as `--space-s`; that is a separate scope. Editor chrome injected into the canvas iframe maps admin spacing to `--chrome-space-*` before using it so it does not overwrite the site's Framework spacing.
225+
213226
### Radius
214227

215228
| Token | Value | Use |
@@ -462,6 +475,15 @@ Every color, gradient, and shadow in `src/admin/`, `src/admin/pages/site/`, and
462475

463476
**Exception:** `src/modules/*` is intentionally exempt — those CSS files ship to the published page output where admin tokens are not guaranteed to exist.
464477

478+
### No hardcoded spacing in admin / ui CSS modules
479+
480+
Admin spacing values in `margin*`, `padding*`, `gap`, `row-gap`, `column-gap`, and CSS-authored SVG dimensions use `var(--space-*)` tokens from `src/styles/globals.css`. Gated by `admin-spacing-token-policy.test.ts`.
481+
482+
`padding: 12px 0;`
483+
`padding: var(--space-l) 0;`
484+
485+
**Exception:** `src/modules/*` is intentionally exempt — those CSS files ship to the published page output where admin tokens are not guaranteed to exist.
486+
465487
### No `var(--name, fallback)` in admin / ui CSS modules
466488

467489
Use bare `var(--name)` — never `var(--name, fallback)`. A fallback is either dead code (the token exists — drop the fallback) or a mask for a missing token (define the token in `globals.css` instead). Defaults for JS-driven custom properties belong in a CSS rule (`[data-x]` selector or `:root`), not scattered in every `var()` reader. Gated by `no-css-var-fallbacks.test.ts`.
@@ -556,7 +578,7 @@ The HTML `title` attribute is banned for hover hints — gated by `no-native-tit
556578

557579
1. Create `src/ui/components/<Name>/<Name>.tsx`, `<Name>.module.css`, and `index.ts`.
558580
2. Re-export from `src/ui/components/index.ts` so consumers import from `@ui/components`.
559-
3. The primitive must work with the existing tokens — do not introduce new colors or radii to support it. If you need new tokens, see "Adding a new design token" first.
581+
3. The primitive must work with the existing tokens — do not introduce new colors, radii, font sizes, or spacing values to support it. If you need new tokens, see "Adding a new design token" first.
560582
4. If it replaces a bare HTML control (`button`, `input`, etc.), update the matching architecture test's allowlist or gate.
561583
5. Document it in the components table above and (if it has non-obvious usage) write a short [docs/reference/ui-primitives.md](reference/ui-primitives.md) entry.
562584

@@ -588,6 +610,7 @@ The HTML `title` attribute is banned for hover hints — gated by `no-native-tit
588610
- `vendor/pixel-art-icons/` — vendored icon set
589611
- Gate tests:
590612
- `src/__tests__/architecture/css-token-policy.test.ts` — no hardcoded colors in admin / ui CSS modules
613+
- `src/__tests__/architecture/admin-spacing-token-policy.test.ts` — admin / ui margin, padding, gap, and CSS-authored SVG dimensions use fluid `--space-*` tokens
591614
- `src/__tests__/architecture/no-css-var-fallbacks.test.ts` — no `var(--name, fallback)` in admin / ui CSS modules
592615
- `src/__tests__/architecture/scrollbar-chrome.test.ts` — scrollbar tokens declared in `globals.css`; both Firefox and WebKit/Blink implementations use them; properties panel uses `scrollbar-gutter: stable`
593616
- `src/__tests__/architecture/noTailwindUtilities.test.ts` — no Tailwind utility classes (covers all palette names)

docs/editor.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ Each iframe `<head>` receives five `<style>` elements (three from `ClassStyleInj
431431

432432
The **unlayered-vs-layered** split is the cascade isolation mechanism: CSS rules outside any `@layer` always beat rules inside `@layer`-d blocks, regardless of specificity. Author CSS (both the class registry and user stylesheets) goes into `@layer user-authored`, so it can never override the editor chrome even with a high-specificity selector.
433433

434-
`EditorChromeInjector` targets chrome elements via **stable data-attribute selectors** (`data-canvas-module-placeholder`, `data-instatic-slot-instance`, `data-instatic-unknown-module`, etc.) rather than hashed CSS-Module class names, which only exist in the parent document. At mount, it copies the required safe design tokens (`--text-subtle`, `--canvas-placeholder-bg`, `--radius`, etc.) from the parent document's `:root` onto the iframe's `:root` so `var(...)` references resolve correctly inside the iframe. Admin font and text-size tokens are remapped to `--chrome-font-sans` and `--chrome-text-*` before use; they are never copied as `--font-sans` or `--text-*`, because those short names belong to the rendered site's Framework tokens inside the canvas.
434+
`EditorChromeInjector` targets chrome elements via **stable data-attribute selectors** (`data-canvas-module-placeholder`, `data-instatic-slot-instance`, `data-instatic-unknown-module`, etc.) rather than hashed CSS-Module class names, which only exist in the parent document. At mount, it copies the required safe design tokens (`--text-subtle`, `--canvas-placeholder-bg`, `--radius`, etc.) from the parent document's `:root` onto the iframe's `:root` so `var(...)` references resolve correctly inside the iframe. Admin font, text-size, and spacing tokens are remapped to `--chrome-font-sans`, `--chrome-text-*`, and `--chrome-space-*` before use; they are never copied as `--font-sans`, `--text-*`, or `--space-*`, because those short names belong to the rendered site's Framework tokens inside the canvas.
435435

436436
Full details: [`docs/features/canvas-iframe-per-frame.md`](../features/canvas-iframe-per-frame.md).
437437

@@ -663,7 +663,7 @@ See [docs/features/plugin-system.md](features/plugin-system.md) for the plugin S
663663
## Styling
664664

665665
- **CSS Modules only.** `Component.module.css` next to `Component.tsx`. Gated by `noTailwindUtilities.test.ts`.
666-
- **Tokens from `src/styles/globals.css`** — no hardcoded hex / rgb / hsl in admin or ui CSS modules. Gated by `css-token-policy.test.ts`.
666+
- **Tokens from `src/styles/globals.css`** — no hardcoded hex / rgb / hsl in admin or ui CSS modules, and no hardcoded pixel font sizes or spacing for admin chrome. Gated by `css-token-policy.test.ts`, `admin-typography-token-policy.test.ts`, and `admin-spacing-token-policy.test.ts`.
667667
- **UI primitives from `src/ui/components/`** — see [docs/design.md](design.md) for the full catalog.
668668
- **In-house `cn`** from `@ui/cn` — no `clsx`, `tailwind-merge`, `cva`, `@radix-ui/*`. Gated by `no-tailwind-deps.test.ts`.
669669

docs/features/canvas-iframe-per-frame.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Five `<style>` elements are injected per iframe (three from `ClassStyleInjector`
110110

111111
| Injector | `id` attribute | Cascade layer | Purpose |
112112
|---|---|---|---|
113-
| `EditorChromeInjector` | `instatic-editor-chrome` | **unlayered** | Editor chrome: placeholder, slot-instance, unknown-module styles. Copies safe required design tokens from parent `:root` onto iframe `:root`. The editor UI font and admin text-size tokens are forwarded as **chrome-namespaced** `--chrome-font-sans` and `--chrome-text-*` aliases, not as `--font-sans` or `--text-*`, because the injector is unlayered and would otherwise clobber the site's own Framework tokens. |
113+
| `EditorChromeInjector` | `instatic-editor-chrome` | **unlayered** | Editor chrome: placeholder, slot-instance, unknown-module styles. Copies safe required design tokens from parent `:root` onto iframe `:root`. The editor UI font, admin text-size tokens, and admin spacing tokens are forwarded as **chrome-namespaced** `--chrome-font-sans`, `--chrome-text-*`, and `--chrome-space-*` aliases, not as `--font-sans`, `--text-*`, or `--space-*`, because the injector is unlayered and would otherwise clobber the site's own Framework tokens. |
114114
| `ClassStyleInjector` | `mc-classes` | `@layer user-authored` | Publisher reset + framework CSS + class registry CSS |
115115
| `ClassStyleInjector` | `mc-classes-preview` | `@layer user-authored` | Higher-specificity preview rule (doubled selector) while a property control is hovered. Empty for state-pseudo rules — those use `mc-classes-force-state` instead. |
116116
| `ClassStyleInjector` | `mc-classes-force-state` | `@layer user-authored` | Force-paints the active state-pseudo rule (`.btn:hover`, `.card:focus`, etc.) onto the selected node via a doubled `[data-node-id]` selector so the state is visible/editable without physically triggering it. Mirrors the full `contextStyles` emission per breakpoint and condition. |

docs/reference/design-tokens.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# Design Tokens
22

3-
The complete catalog of design tokens declared in `src/styles/globals.css`. Every color, radius, shadow, font, font size, and z-index used by the admin / editor / UI primitives is here. CSS Modules in `src/admin/`, `src/admin/pages/site/`, and `src/ui/` MUST reference these via `var(--*)` — hardcoded hex / rgb / hsl is gated by `css-token-policy.test.ts`, and hardcoded font-size pixels are gated by `admin-typography-token-policy.test.ts`.
3+
The complete catalog of design tokens declared in `src/styles/globals.css`. Every color, radius, shadow, font, font size, spacing value, and z-index used by the admin / editor / UI primitives is here. CSS Modules in `src/admin/`, `src/admin/pages/site/`, and `src/ui/` MUST reference these via `var(--*)` — hardcoded hex / rgb / hsl is gated by `css-token-policy.test.ts`, hardcoded font-size pixels are gated by `admin-typography-token-policy.test.ts`, and hardcoded margin / padding / gap pixels are gated by `admin-spacing-token-policy.test.ts`.
44

55
---
66

77
## TL;DR
88

99
- One file: `src/styles/globals.css`.
10-
- Grouped by role: fonts/fluid type, core surfaces/text/borders, overlays/scrims, identity accents, semantic state, radii, shadows, canvas, syntax, and z-index.
10+
- Grouped by role: fonts/fluid type, fluid spacing, core surfaces/text/borders, overlays/scrims, identity accents, semantic state, radii, shadows, canvas, syntax, and z-index.
1111
- **Two-layer color model**: achromatic base + semantic / categorical color layer on top. See [docs/design.md](../design.md) for the rationale.
1212
- Add a new token by editing `globals.css`. Reference it with `var(--your-token)` in CSS Modules.
1313
- The `src/modules/` directory is **exempt** from the no-hardcoded-color rule — module CSS ships to published pages where admin tokens are not guaranteed to exist.
@@ -49,6 +49,37 @@ These are admin tokens. The published-site Framework engine also emits short nam
4949

5050
---
5151

52+
## Fluid spacing scale
53+
54+
Admin UI spacing uses the Core Framework-style `--space-*` scale directly. Use these tokens for CSS-authored `margin`, `padding`, `gap`, `row-gap`, `column-gap`, and SVG width/height values in admin and shared UI CSS modules. Keep true hairlines on `--space-px`.
55+
56+
| Token | Fluid range | Typical use |
57+
|----------------|-------------|----------------------------------------------|
58+
| `--space-px` | 1px fixed | Hairline gaps and exact 1px spacing |
59+
| `--space-4xs` | 2px → 3px | Micro offsets, dense icon nudges |
60+
| `--space-3xs` | 3px → 4px | Very tight inline padding |
61+
| `--space-2xs` | 4px → 5px | Tight chip gaps |
62+
| `--space-xs` | 5px → 6px | Compact control gaps |
63+
| `--space-s` | 6px → 8px | Default dense row gaps |
64+
| `--space-m` | 8px → 10px | Default panel/control spacing |
65+
| `--space-l` | 10px → 12px | Section row padding |
66+
| `--space-xl` | 12px → 14px | Dialog/control interior padding |
67+
| `--space-2xl` | 14px → 16px | Larger panel gaps |
68+
| `--space-3xl` | 16px → 18px | Icon-size SVGs and roomy control gaps |
69+
| `--space-4xl` | 18px → 20px | Compact empty-state spacing |
70+
| `--space-5xl` | 20px → 24px | Card and modal spacing |
71+
| `--space-6xl` | 24px → 28px | Large controls / icon tiles |
72+
| `--space-7xl` | 28px → 32px | Page-section rhythm |
73+
| `--space-8xl` | 32px → 40px | Large page body spacing |
74+
| `--space-9xl` | 40px → 48px | Prominent empty-state spacing |
75+
| `--space-10xl` | 48px → 56px | Large dashboard/display spacing |
76+
| `--space-11xl` | 56px → 72px | Large SVG previews and major vertical gaps |
77+
| `--space-12xl` | 84px → 108px| Largest admin display spacing |
78+
79+
These are admin tokens. The published-site Framework engine also emits short names such as `--space-s`; those belong to the generated site CSS, not admin chrome. Editor chrome injected into the canvas iframe maps admin spacing to `--chrome-space-*` before using it so it cannot override the site's Framework spacing.
80+
81+
---
82+
5283
## Surface hierarchy (achromatic base)
5384

5485
Six surface tones for depth. Lighter is higher in the stack.

docs/reference/ui-primitives.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Every interactive control in `src/admin/` goes through one of these. Bare `<butt
1111
- Import from `@ui/components/<Name>` — each primitive lives in its own folder with `Component.tsx`, `Component.module.css`, and `index.ts`.
1212
- The 30 primitives below cover every interactive control in the admin. If something's missing, add a new primitive (see "Adding a new primitive" below) — don't reach for a third-party library.
1313
- Composition uses `cn` from `@ui/cn` — a 3-line in-house helper. **Never** `clsx` / `tailwind-merge` / `cva` / `@radix-ui/*` — gated by `no-tailwind-deps.test.ts`.
14-
- All colors, radii, and admin font sizes come from CSS custom properties in `src/styles/globals.css` — see [docs/reference/design-tokens.md](design-tokens.md).
14+
- All colors, radii, admin font sizes, and admin spacing values come from CSS custom properties in `src/styles/globals.css` — see [docs/reference/design-tokens.md](design-tokens.md).
1515
- Forbidden: Tailwind classes, hardcoded hex, inline `style` (except dynamic CSS custom properties), `!important`, native `title=` tooltips, native `alert()` / `confirm()`.
1616

1717
---
@@ -591,14 +591,14 @@ Use this when the value is genuinely runtime (resize handle drag, computed bbox,
591591

592592
1. Create `src/ui/components/<Name>/<Name>.tsx`, `<Name>.module.css`, `index.ts`.
593593
2. Re-export from `src/ui/components/index.ts` so consumers import from `@ui/components`.
594-
3. CSS uses tokens from `src/styles/globals.css` — never hardcoded colors.
594+
3. CSS uses tokens from `src/styles/globals.css` — never hardcoded colors, font sizes, or spacing values.
595595
4. Composition uses `cn` from `@ui/cn`.
596596
5. Icons come from `pixel-art-icons/icons/<name>` (deep-imported).
597597
6. If it replaces a bare HTML control (`<button>` etc.), update the matching architecture test (e.g. `button-primitive-usage.test.ts`).
598598
7. Add a row to the table above in this doc.
599599
8. Add a one-line entry to [docs/design.md](../design.md) "Components" table.
600600

601-
The primitive must work entirely with existing design tokens. If you need a new token, add it to `globals.css` first and update [docs/reference/design-tokens.md](design-tokens.md).
601+
The primitive must work entirely with existing design tokens. If you need a new color, radius, font-size, or spacing token, add it to `globals.css` first and update [docs/reference/design-tokens.md](design-tokens.md).
602602

603603
---
604604

0 commit comments

Comments
 (0)