@syncedco/flow 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ ## 0.3.2 - 2026-06-15
4
+
5
+ - Add an explicit accessible root typography baseline with `html { font-size: 100%; }`.
6
+ - Add `--sf-text-base` and generated `--text-base` aliases for fluid body text.
7
+ - Use the new fluid body text token in shipped and generated base CSS.
8
+ - Convert CLI-generated utility lengths for blur, rings, outlines, and thicker borders from static `px` to `rem`.
9
+ - Extend guardrails and tests so shipped generated examples reject raw pixels outside allowed hairline, visually-hidden, and forced-colors exceptions.
10
+
11
+ ## 0.3.1 - 2026-06-13
12
+
13
+ - Fix mobile app drawer scrolling by giving the drawer sidebar a viewport block size.
14
+ - Vertically center `.sf-tab` contents.
15
+ - Improve built-in dark theme token coverage for primary, state, soft, ring, and glow tokens.
16
+ - Make `.sf-filter-bar` alignment token-configurable with a centered default.
17
+ - Add a status-chip dot opt-out with `data-dot="false"` and `sf-status--label`.
18
+ - Add reduced-motion safeguards for hover lift and marquee animation.
19
+ - Add public `--sf-z-*` stacking tokens for header, backdrop, drawer, overlay, toast, sticky, and skip-link layers.
20
+ - Reframe positioning copy from "AI-native" to "AI-friendly".
21
+ - Document the global theme config scope and the project-CSS pattern for advanced theme matrices.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Synced Flow
2
2
 
3
- AI-native fluid CSS design system for brand-consistent websites, agency builds,
3
+ AI-friendly fluid CSS design system for brand-consistent websites, agency builds,
4
4
  and modern frontend projects.
5
5
 
6
6
  Synced Flow gives developers and AI coding agents a shared styling contract:
@@ -58,6 +58,7 @@ documentation, and recipes.
58
58
  Useful project files:
59
59
 
60
60
  - [Contributing](CONTRIBUTING.md)
61
+ - [Changelog](CHANGELOG.md)
61
62
  - [Security policy](SECURITY.md)
62
63
  - [Code of conduct](CODE_OF_CONDUCT.md)
63
64
  - [Support](SUPPORT.md)
package/base.css CHANGED
@@ -4,6 +4,7 @@
4
4
  html {
5
5
  background: var(--sf-colour-background);
6
6
  color: var(--sf-colour-foreground);
7
+ font-size: 100%;
7
8
  scroll-behavior: smooth;
8
9
  }
9
10
 
@@ -11,7 +12,7 @@
11
12
  background: var(--sf-colour-background);
12
13
  color: var(--sf-colour-foreground);
13
14
  font-family: var(--sf-font-sans);
14
- font-size: var(--sf-type-body);
15
+ font-size: var(--sf-text-base);
15
16
  line-height: var(--sf-line-height-body);
16
17
  -webkit-font-smoothing: antialiased;
17
18
  text-rendering: optimizeLegibility;
@@ -2980,6 +2980,19 @@ function pxToRem(value) {
2980
2980
  })
2981
2981
  }
2982
2982
 
2983
+ function pixelNumberToRem(value) {
2984
+ const number = Number(value)
2985
+ if (Number.isNaN(number)) return null
2986
+ if (number === 0) return '0'
2987
+ return `${round(number / 16)}rem`
2988
+ }
2989
+
2990
+ function borderWidthUnit(value) {
2991
+ if (value === '0') return '0'
2992
+ if (value === '1') return '1px'
2993
+ return pixelNumberToRem(value)
2994
+ }
2995
+
2983
2996
  function arbitraryValue(value) {
2984
2997
  return pxToRem(value.replace(/_/g, ' '))
2985
2998
  }
@@ -3650,7 +3663,7 @@ function colourUtility(base) {
3650
3663
  function borderUtility(base) {
3651
3664
  if (base === 'border') return [['border-width', '1px']]
3652
3665
  if (base === 'border-0') return [['border-width', '0']]
3653
- if (base === 'border-2') return [['border-width', '2px']]
3666
+ if (base === 'border-2') return [['border-width', '0.125rem']]
3654
3667
  if (base === 'border-t') return [['border-top-width', '1px']]
3655
3668
  if (base === 'border-r') return [['border-right-width', '1px']]
3656
3669
  if (base === 'border-b') return [['border-bottom-width', '1px']]
@@ -3660,7 +3673,7 @@ function borderUtility(base) {
3660
3673
  const sideWidth = base.match(/^border-(t|r|b|l)-(\d+)$/)
3661
3674
  if (sideWidth) {
3662
3675
  const side = { t: 'top', r: 'right', b: 'bottom', l: 'left' }[sideWidth[1]]
3663
- return [[`border-${side}-width`, `${sideWidth[2]}px`]]
3676
+ return [[`border-${side}-width`, borderWidthUnit(sideWidth[2])]]
3664
3677
  }
3665
3678
  if (base === 'divide-y') return [['--sf-divide-y', '1px']]
3666
3679
  if (base === 'divide-x') return [['--sf-divide-x', '1px']]
@@ -3724,26 +3737,26 @@ function effectUtility(base) {
3724
3737
  if (match) return [['opacity', String(Number(match[1]) / 100)]]
3725
3738
  match = base.match(/^blur-(none|sm|md|lg|xl|2xl|3xl)$/)
3726
3739
  if (match) {
3727
- const map = { none: '0', sm: '4px', md: '12px', lg: '16px', xl: '24px', '2xl': '40px', '3xl': '64px' }
3740
+ const map = { none: '0', sm: '0.25rem', md: '0.75rem', lg: '1rem', xl: '1.5rem', '2xl': '2.5rem', '3xl': '4rem' }
3728
3741
  return [['filter', `blur(${map[match[1]]})`]]
3729
3742
  }
3730
3743
  match = base.match(/^backdrop-blur(?:-(none|sm|md|lg|xl))?$/)
3731
3744
  if (match) {
3732
- const map = { undefined: '8px', none: '0', sm: '4px', md: '12px', lg: '16px', xl: '24px' }
3745
+ const map = { undefined: '0.5rem', none: '0', sm: '0.25rem', md: '0.75rem', lg: '1rem', xl: '1.5rem' }
3733
3746
  return [['backdrop-filter', `blur(${map[match[1] ?? 'undefined']})`]]
3734
3747
  }
3735
3748
  match = base.match(/^ring(?:-(\d+))?$/)
3736
- if (match) return [['box-shadow', `0 0 0 ${match[1] ?? 3}px var(--sf-ring-color, var(--color-ring))`]]
3749
+ if (match) return [['box-shadow', `0 0 0 ${pixelNumberToRem(match[1] ?? 3)} var(--sf-ring-color, var(--color-ring))`]]
3737
3750
  if (base === 'ring-offset-white') return [['--sf-ring-offset-color', 'var(--color-white)']]
3738
3751
  if (base === 'ring-offset-background') return [['--sf-ring-offset-color', 'var(--color-background)']]
3739
3752
  match = base.match(/^ring-offset-(\d+)$/)
3740
- if (match) return [['--sf-ring-offset-width', `${match[1]}px`]]
3753
+ if (match) return [['--sf-ring-offset-width', pixelNumberToRem(match[1])]]
3741
3754
  if (base === 'outline') return [['outline-style', 'solid']]
3742
- if (base === 'outline-none') return [['outline', '2px solid transparent'], ['outline-offset', '2px']]
3755
+ if (base === 'outline-none') return [['outline', '0.125rem solid transparent'], ['outline-offset', '0.125rem']]
3743
3756
  match = base.match(/^outline-(\d+)$/)
3744
- if (match) return [['outline-width', `${match[1]}px`]]
3757
+ if (match) return [['outline-width', pixelNumberToRem(match[1])]]
3745
3758
  match = base.match(/^outline-offset-(\d+)$/)
3746
- if (match) return [['outline-offset', `${match[1]}px`]]
3759
+ if (match) return [['outline-offset', pixelNumberToRem(match[1])]]
3747
3760
  match = base.match(/^outline-(.+)$/)
3748
3761
  if (match) {
3749
3762
  const value = colourValue(match[1])
@@ -3944,6 +3957,7 @@ function buildTokensCss() {
3944
3957
 
3945
3958
  ${steps.join('\n')}
3946
3959
  --type-caption-sm: var(--step--2);
3960
+ --text-base: var(--step-0);
3947
3961
 
3948
3962
  ${spaces.join('\n')}
3949
3963
  ${pairs.join('\n')}
@@ -4074,7 +4088,7 @@ function buildBaseCss() {
4074
4088
  return `@layer reset {
4075
4089
  *, *::before, *::after { box-sizing: border-box; }
4076
4090
  *:where(:not(dialog)) { margin: 0; }
4077
- html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
4091
+ html { font-size: 100%; -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
4078
4092
  body { min-block-size: 100%; }
4079
4093
  img, picture, video, canvas, svg { display: block; max-inline-size: 100%; }
4080
4094
  img, video { block-size: auto; }
@@ -4084,11 +4098,15 @@ function buildBaseCss() {
4084
4098
  }
4085
4099
 
4086
4100
  @layer base {
4101
+ html {
4102
+ font-size: 100%;
4103
+ }
4104
+
4087
4105
  body {
4088
4106
  background: var(--color-background);
4089
4107
  color: var(--color-foreground);
4090
4108
  font-family: var(--font-inter, var(--sf-font-sans, ui-sans-serif, system-ui, sans-serif));
4091
- font-size: var(--step-0);
4109
+ font-size: var(--text-base);
4092
4110
  line-height: 1.5;
4093
4111
  font-feature-settings: "rlig" 1, "calt" 1;
4094
4112
  -webkit-font-smoothing: antialiased;
@@ -4116,7 +4134,7 @@ function buildBaseCss() {
4116
4134
  :where(button:disabled, [aria-disabled="true"]) { cursor: not-allowed; }
4117
4135
 
4118
4136
  :where(:focus-visible) {
4119
- outline: 2px solid var(--color-ring);
4137
+ outline: 0.125rem solid var(--color-ring);
4120
4138
  outline-offset: .2rem;
4121
4139
  }
4122
4140
 
package/components.css CHANGED
@@ -432,6 +432,16 @@
432
432
  to { transform: translateX(-50%); }
433
433
  }
434
434
 
435
+ @media (prefers-reduced-motion: reduce) {
436
+ :where(.sf-button:hover, .sf-card--interactive:hover) {
437
+ transform: none;
438
+ }
439
+
440
+ .sf-marquee__track {
441
+ animation: none;
442
+ }
443
+ }
444
+
435
445
  .sf-command-list {
436
446
  display: grid;
437
447
  font-family: var(--sf-font-mono);
@@ -837,7 +847,7 @@
837
847
  }
838
848
 
839
849
  .sf-filter-bar {
840
- align-items: end;
850
+ align-items: var(--sf-filter-bar-align, center);
841
851
  background: var(--sf-colour-surface);
842
852
  border: 1px solid var(--sf-colour-border);
843
853
  border-radius: var(--sf-radius-panel);
@@ -984,6 +994,10 @@
984
994
  inline-size: .42em;
985
995
  }
986
996
 
997
+ :where(.sf-status[data-dot="false"], .sf-status--label)::before {
998
+ content: none;
999
+ }
1000
+
987
1001
  .sf-status[data-tone="success"] {
988
1002
  --sf-status-bg: var(--sf-colour-success-soft);
989
1003
  --sf-status-border: color-mix(in oklch, var(--sf-colour-success) 34%, transparent);
@@ -1418,6 +1432,7 @@
1418
1432
  :where(.sf-popover, .sf-menu-popover, .sf-tooltip, .sf-toast, .sf-banner, .sf-drawer):popover-open {
1419
1433
  display: grid;
1420
1434
  gap: var(--sf-space-xs);
1435
+ z-index: var(--sf-z-overlay);
1421
1436
  }
1422
1437
 
1423
1438
  .sf-popover {
@@ -1489,7 +1504,7 @@
1489
1504
  inset-block-start: var(--sf-space-s);
1490
1505
  inset-inline-end: var(--sf-space-s);
1491
1506
  position: fixed;
1492
- z-index: 50;
1507
+ z-index: var(--sf-z-toast);
1493
1508
  }
1494
1509
 
1495
1510
  .sf-banner {
@@ -1604,6 +1619,7 @@
1604
1619
  }
1605
1620
 
1606
1621
  .sf-tab {
1622
+ align-items: center;
1607
1623
  border-block-end: 0.125rem solid transparent;
1608
1624
  border-radius: var(--sf-radius-full);
1609
1625
  color: var(--sf-colour-muted);
@@ -0,0 +1,113 @@
1
+ # Synced Flow Audit — June 2026 (v0.3.0)
2
+
3
+ Independent audit of `@syncedco/flow` at v0.3.0 (`a8a1f68`), run across three
4
+ passes: product coherence (claims vs shipped), CSS quality, and tooling/release
5
+ engineering. Findings are grounded in the source and in real consumer feedback
6
+ from the Synced CRM (`synced-crm`, the first production app consumer).
7
+
8
+ ## Verdict
9
+
10
+ Flow delivers its **original model (websites/agency) almost completely** and
11
+ the **engineering discipline behind it impressively**. It falls short in three
12
+ places: **app-tier depth**, **advanced theming scope**, and a few product
13
+ positioning/docs gaps. It is a real, well-built system, and the positioning now
14
+ fits better as AI-friendly / agent-ready.
15
+
16
+ | Dimension | Rating | Summary |
17
+ |---|---|---|
18
+ | Fluid foundation (tokens, scales, layout) | ✅ Excellent | Genuine Utopia-style fluidity; modern CSS (layers, oklch, logical props, container queries); clean layer hygiene |
19
+ | Website/agency coverage | ✅ ~90% | Everything claimed ships; agency-ready without custom CSS |
20
+ | Tooling & release engineering | ✅ Excellent | 14 tested CLI commands, guardrails (size budgets, zero-dep, px policy), 40/40 tests, automated tag-driven publishing |
21
+ | App/SaaS tier (0.2–0.3) | ⚠️ ~60% | Real scaffolding (proven by the CRM) but thin; consumers carry 20–40% custom CSS |
22
+ | Dark mode | ✅ Patched after audit | `.sf-theme-dark` now overrides primary/status/soft/ring/glow tokens rather than inheriting light-tuned values |
23
+ | Theming config | ✅ Documented boundary | Config handles global light/dark token themes; advanced per-surface/multi-variant theming belongs in project CSS token scopes |
24
+ | AI positioning | ✅ Reframed | Positioning now uses AI-friendly / agent-ready language for the shipped CLI, skill, and agent guidance |
25
+ | Docs | ⚠️ Improving | Website docs thorough; CHANGELOG and theming-scope docs added; app-tier docs can still mature |
26
+
27
+ ## Confirmed bugs (found by the CRM, verified in source)
28
+
29
+ 1. **Drawer can't scroll** — fixed after audit by adding `block-size: 100dvh`
30
+ to `.sf-app-shell[data-mobile="drawer"] .sf-app-sidebar`, giving the
31
+ existing `overflow: auto` a real viewport-height scroll container.
32
+ 2. **`.sf-tab` not vertically centred** — fixed after audit with
33
+ `align-items: center`.
34
+ 3. **`.sf-filter-bar` `align-items: end`** — fixed after audit with
35
+ `align-items: var(--sf-filter-bar-align, center)`, preserving a project
36
+ escape hatch without adding a new component variant.
37
+ 4. **`.sf-status` dot is mandatory** — fixed after audit with
38
+ `data-dot="false"` and `sf-status--label` opt-outs.
39
+
40
+ ## Higher-severity findings the CRM did NOT hit
41
+
42
+ These mattered because no consumer had exercised them yet. They are now patched
43
+ or documented, but should still be validated in the next real consumer.
44
+
45
+ 5. **Dark mode token coverage is critically incomplete** — fixed after audit
46
+ for the shipped base theme by adding dark-mode primary, state, soft, ring,
47
+ and glow token overrides.
48
+ 6. **Z-index scale is compressed and undocumented** — fixed after audit with
49
+ public `--sf-z-*` tokens wired into header, backdrop, drawer, overlay, toast,
50
+ sticky, and skip-link layers.
51
+ 7. **Missing `prefers-reduced-motion` guards** — fixed after audit for
52
+ `.sf-button:hover`, `.sf-card--interactive:hover`, and the marquee track.
53
+
54
+ ## Theming model limitation (real, undocumented)
55
+
56
+ Config supports global light + `darkColours` + per-preset overrides. It does
57
+ not try to express per-surface / multi-variant theming (e.g. `[data-theme]` ×
58
+ `[data-surface]` matrices, per-tenant white-label, density-by-context). That is
59
+ a legitimate design choice for Synced Flow's scope, and `config-reference.md`
60
+ now documents the hand-written token-scope pattern for advanced cases.
61
+
62
+ ## AI-Friendly Positioning
63
+
64
+ What ships is real and good: `catalog --json`, `suggest`, `pattern`, `recipe`,
65
+ `lint`, `doctor`, a packaged skill, and `agents install` for 8 targets. The
66
+ project now describes this as **AI-friendly** and agent-readable tooling rather
67
+ than implying runtime AI integration.
68
+
69
+ ## App-tier coverage gaps (from real CRM adoption)
70
+
71
+ Shipped and adopted: app-shell, data-table, empty-state, filter-bar, status
72
+ chips, tabs, toast skin, form field shells. Still missing for production app
73
+ work (CRM kept custom or deferred): combobox/searchable select, pagination
74
+ depth (page size, jump-to-page), modal-with-form as a primitive (not just a
75
+ pattern), bulk-actions toolbar, multi-step/wizard, skeleton loading compounds,
76
+ sticky-header + horizontal-scroll tables. Kanban/timesheet/invoice-doc are
77
+ deliberately consumer-specific and out of scope.
78
+
79
+ ## Strengths (keep doing)
80
+
81
+ - Real fluidity; modern CSS; excellent cascade-layer hygiene.
82
+ - Tooling/release engineering is the strongest area: guardrails enforce
83
+ zero-dependency, per-layer gzip size budgets, layer order, and a raw-px
84
+ policy; 40/40 tests pass; tag-driven OIDC publishing; complete exports map.
85
+ - CLI catalog currently in sync with shipped CSS (verified) — though by hand.
86
+ - AI agent guidance (skill + `agents install`) is current with 0.3.0 primitives.
87
+
88
+ ## Recommended roadmap
89
+
90
+ **0.3.1 — bug patch (small, soon):**
91
+ 1. Drawer viewport scroll container. ✅
92
+ 2. `.sf-tab` `align-items: center`. ✅
93
+ 3. `.sf-filter-bar` tokenized alignment with centered default. ✅
94
+ 4. `.sf-status` dot opt-out. ✅
95
+ 5. `prefers-reduced-motion` guards on hover transforms + marquee. ✅
96
+
97
+ **0.4.0 — structural gaps:**
98
+ 6. Continue dark-mode validation with a real consumer using Flow's built-in
99
+ theme selectors.
100
+ 7. Begin app-tier depth: combobox, pagination, modal-with-form, bulk-actions —
101
+ prioritised by what the CRM had to keep custom.
102
+
103
+ **Docs / positioning / CI (cheap, high-trust):**
104
+ 8. CI: smoke-build the examples; add a test asserting the CLI catalog matches
105
+ the shipped CSS (in sync today only by hand); consider a CSS parse/lint and
106
+ a small visual-regression pass.
107
+
108
+ ## Meta-observation
109
+
110
+ The four bugs the CRM surfaced are exactly the class only a real consumer finds
111
+ — the proving-ground loop is working as intended. The most useful next
112
+ validation step is getting a second consumer, or the CRM itself, onto Flow's
113
+ built-in dark mode and app primitives before 1.0.
@@ -111,3 +111,8 @@ The base layer uses cascade layers, logical properties, low-specificity
111
111
  `prefers-reduced-motion`. Utopia informs the fluid type, space, and grid
112
112
  tokens; Synced Flow owns the reset, accessibility helpers, and component
113
113
  defaults.
114
+
115
+ The root element is kept at `font-size: 100%` rather than a fixed pixel value so
116
+ browser and user font-size settings remain respected. Body copy starts from the
117
+ fluid text token with `body { font-size: var(--sf-text-base); }`, where
118
+ `--sf-text-base` maps to the existing body type scale.
@@ -79,3 +79,30 @@ theme: {
79
79
  },
80
80
  }
81
81
  ```
82
+
83
+ ## Theme Scope
84
+
85
+ The config theme model is intentionally global. Use `theme.colours` for the
86
+ default token set and `theme.darkColours` for `.sf-theme-dark` /
87
+ `[data-sf-theme="dark"]` overrides. This covers the common site or app case:
88
+ one brand token set, with an optional dark-mode token set.
89
+
90
+ Synced Flow does not try to encode every theme matrix in config. If a project
91
+ needs per-tenant branding, `[data-surface]` variants, density-by-context, or
92
+ other multi-axis theme rules, keep the reusable defaults in config and write
93
+ the extra token scopes in project CSS:
94
+
95
+ ```css
96
+ [data-tenant="acme"] {
97
+ --sf-colour-primary: oklch(62% 0.19 252);
98
+ --sf-colour-primary-hover: oklch(56% 0.19 252);
99
+ }
100
+
101
+ [data-surface="inset"] {
102
+ --sf-colour-surface: var(--sf-colour-surface-inset);
103
+ --sf-card-padding: var(--sf-space-s);
104
+ }
105
+ ```
106
+
107
+ Use this pattern for deliberate product theming. Avoid scattering one-off
108
+ colour values through page components when a value can live in a token scope.
@@ -10,13 +10,14 @@ Use the `--sf-*` variables as the stable CSS foundation.
10
10
  | Layer | Examples | Use for |
11
11
  | --- | --- | --- |
12
12
  | Font | `--sf-font-sans`, `--sf-font-display`, `--sf-font-mono` | Site typography families. |
13
- | Type | `--sf-type-body`, `--sf-type-lead`, `--sf-type-h1`, `--sf-type-display` | Fluid text sizes. |
13
+ | Type | `--sf-text-base`, `--sf-type-body`, `--sf-type-lead`, `--sf-type-h1`, `--sf-type-display` | Fluid text sizes. |
14
14
  | Space | `--sf-space-s`, `--sf-space-m-l`, `--sf-space-xl-2xl` | Fluid padding, margin, and gaps. |
15
15
  | Radius | `--sf-radius-control`, `--sf-radius-panel`, `--sf-radius-full` | Controls, panels, pills. |
16
16
  | Colour | `--sf-colour-background`, `--sf-colour-surface`, `--sf-colour-primary` | Semantic UI colour roles. |
17
17
  | App colour | `--sf-colour-surface-hover`, `--sf-colour-surface-raised-2`, `--sf-colour-border-subtle`, `--sf-colour-backdrop` | App surfaces, hover states, subtle dividers, and overlays. |
18
18
  | Categorical colour | `--sf-colour-categorical-1` ... `--sf-colour-categorical-6` | Charts, stages, labels, and recurring data accents. |
19
19
  | State | `--sf-colour-success`, `--sf-colour-warning`, `--sf-colour-danger`, `--sf-colour-info` | Feedback and notices. |
20
+ | Z-index | `--sf-z-header`, `--sf-z-backdrop`, `--sf-z-drawer`, `--sf-z-overlay`, `--sf-z-toast` | Shared stacking slots for app chrome, drawers, popovers, and feedback. |
20
21
  | Motion | `--sf-duration-fast`, `--sf-duration-normal`, `--sf-ease-standard` | Consistent transitions. |
21
22
  | Component | `--sf-button-*`, `--sf-card-*`, `--sf-input-*`, `--sf-alert-*` | Reusable component defaults. |
22
23
  | Accessibility | `:focus-visible`, `:target`, `[aria-invalid]`, `[aria-current]`, `[aria-expanded]`, `[aria-selected]`, `[aria-busy]`, `[aria-disabled]` | Visible native and ARIA states. |
package/docs/tokens.md CHANGED
@@ -64,10 +64,15 @@ Type and space tokens use Utopia-style `clamp()` values.
64
64
 
65
65
  ```css
66
66
  --sf-step-0: clamp(1rem, 0.9617rem + 0.1701vw, 1.125rem);
67
+ --sf-type-body: var(--sf-step-0);
68
+ --sf-text-base: var(--sf-type-body);
67
69
  --sf-space-s-l: clamp(1rem, 0.6173rem + 1.7007vw, 2.25rem);
68
70
  ```
69
71
 
70
- Prefer these tokens for new project CSS instead of fixed pixel values.
72
+ Prefer these tokens for new project CSS instead of fixed pixel values. The root
73
+ `html` element stays at `font-size: 100%` so browser and user accessibility
74
+ settings remain in control; Synced Flow starts its fluid body scale from
75
+ `--sf-text-base`.
71
76
 
72
77
  ## Core Starter Tokens
73
78
 
@@ -86,6 +91,8 @@ The generated core includes enough semantic variables for common website UI:
86
91
  - structure: `--sf-colour-border-subtle`, `--sf-colour-border`,
87
92
  `--sf-colour-border-strong`, `--sf-colour-backdrop`,
88
93
  `--sf-radius-control`, `--sf-radius-panel`, `--sf-shadow-*`
94
+ - stacking: `--sf-z-sticky`, `--sf-z-header`, `--sf-z-backdrop`,
95
+ `--sf-z-drawer`, `--sf-z-overlay`, `--sf-z-toast`, `--sf-z-skip-link`
89
96
  - data accents: `--sf-colour-categorical-1` through
90
97
  `--sf-colour-categorical-6`
91
98
 
@@ -22,7 +22,7 @@ Synced Flow is designed to reduce the number of styling decisions made from scra
22
22
 
23
23
  Synced Flow is not trying to be a Tailwind clone, a Bootstrap replacement, or a React component library.
24
24
 
25
- It is an AI-native fluid CSS design system for brand-consistent websites and agency builds.
25
+ It is an AI-friendly fluid CSS design system for brand-consistent websites and agency builds.
26
26
 
27
27
  Use it when you want:
28
28
 
@@ -19,6 +19,8 @@
19
19
  --step-6: clamp(2.986rem, 2.5863rem + 1.7763vw, 4.2915rem);
20
20
  --step-7: clamp(3.5832rem, 3.0379rem + 2.4235vw, 5.3644rem);
21
21
  --step-8: clamp(4.2998rem, 3.5634rem + 3.2731vw, 6.7055rem);
22
+ --type-caption-sm: var(--step--2);
23
+ --text-base: var(--step-0);
22
24
 
23
25
  --space-3xs: clamp(0.25rem, 0.2404rem + 0.0425vw, 0.2813rem);
24
26
  --space-2xs: clamp(0.5rem, 0.4809rem + 0.085vw, 0.5625rem);
@@ -88,7 +90,9 @@
88
90
  --color-destructive: var(--sf-colour-destructive, oklch(55% 0.2 28));
89
91
  --color-destructive-foreground: var(--sf-colour-destructive-foreground, oklch(98% 0.01 95));
90
92
  --color-border: var(--sf-colour-border, oklch(100% 0 0 / 0.12));
93
+ --color-border-subtle: var(--sf-colour-border-subtle, oklch(100% 0 0 / 0.08));
91
94
  --color-input: var(--sf-colour-input, oklch(100% 0 0 / 0.12));
95
+ --color-backdrop: var(--sf-colour-backdrop, oklch(0% 0 0 / 0.56));
92
96
  --color-ring: var(--sf-colour-ring, oklch(69% 0.18 44));
93
97
  --color-heading: var(--sf-colour-heading, oklch(20% 0.02 18));
94
98
  --color-page-text: var(--sf-colour-page-text, oklch(23% 0.02 18));
@@ -97,6 +101,14 @@
97
101
  --color-subdued: var(--sf-colour-subdued, oklch(42% 0.02 20));
98
102
  --color-surface: var(--sf-colour-surface, oklch(96% 0.015 80));
99
103
  --color-surface-alt: var(--sf-colour-surface-alt, oklch(96.6% 0.018 82));
104
+ --color-surface-hover: var(--sf-colour-surface-hover, color-mix(in oklch, var(--color-surface-alt) 86%, var(--color-primary)));
105
+ --color-surface-raised-2: var(--sf-colour-surface-raised-2, color-mix(in oklch, var(--color-surface) 86%, white));
106
+ --color-categorical-1: var(--sf-colour-categorical-1, var(--color-primary));
107
+ --color-categorical-2: var(--sf-colour-categorical-2, var(--color-green));
108
+ --color-categorical-3: var(--sf-colour-categorical-3, var(--color-deco-blue));
109
+ --color-categorical-4: var(--sf-colour-categorical-4, var(--color-deco-lime));
110
+ --color-categorical-5: var(--sf-colour-categorical-5, var(--color-warm-icon));
111
+ --color-categorical-6: var(--sf-colour-categorical-6, var(--color-purple-icon));
100
112
  --color-hero-bg: oklch(8% 0.02 260);
101
113
  --color-green: var(--sf-colour-green, oklch(55% 0.12 148));
102
114
  --color-green-dark: var(--sf-colour-green-dark, oklch(45% 0.12 148));
@@ -168,6 +180,8 @@
168
180
  --color-surface: var(--sf-colour-surface);
169
181
  --sf-colour-surface-alt: oklch(94% 0.02 80);
170
182
  --color-surface-alt: var(--sf-colour-surface-alt);
183
+ --sf-colour-surface-elevated: var(--sf-colour-surface-raised);
184
+ --color-surface-elevated: var(--sf-colour-surface-elevated);
171
185
  --sf-colour-border: oklch(18% 0.022 55 / 0.14);
172
186
  --color-border: var(--sf-colour-border);
173
187
  --sf-colour-primary: oklch(58% 0.17 42);
@@ -19,6 +19,8 @@
19
19
  --step-6: clamp(2.986rem, 2.5863rem + 1.7763vw, 4.2915rem);
20
20
  --step-7: clamp(3.5832rem, 3.0379rem + 2.4235vw, 5.3644rem);
21
21
  --step-8: clamp(4.2998rem, 3.5634rem + 3.2731vw, 6.7055rem);
22
+ --type-caption-sm: var(--step--2);
23
+ --text-base: var(--step-0);
22
24
 
23
25
  --space-3xs: clamp(0.25rem, 0.2404rem + 0.0425vw, 0.2813rem);
24
26
  --space-2xs: clamp(0.5rem, 0.4809rem + 0.085vw, 0.5625rem);
@@ -88,7 +90,9 @@
88
90
  --color-destructive: var(--sf-colour-destructive, oklch(55% 0.2 28));
89
91
  --color-destructive-foreground: var(--sf-colour-destructive-foreground, oklch(98% 0.01 95));
90
92
  --color-border: var(--sf-colour-border, oklch(100% 0 0 / 0.12));
93
+ --color-border-subtle: var(--sf-colour-border-subtle, oklch(100% 0 0 / 0.08));
91
94
  --color-input: var(--sf-colour-input, oklch(100% 0 0 / 0.12));
95
+ --color-backdrop: var(--sf-colour-backdrop, oklch(0% 0 0 / 0.56));
92
96
  --color-ring: var(--sf-colour-ring, oklch(69% 0.18 44));
93
97
  --color-heading: var(--sf-colour-heading, oklch(20% 0.02 18));
94
98
  --color-page-text: var(--sf-colour-page-text, oklch(23% 0.02 18));
@@ -97,6 +101,14 @@
97
101
  --color-subdued: var(--sf-colour-subdued, oklch(42% 0.02 20));
98
102
  --color-surface: var(--sf-colour-surface, oklch(96% 0.015 80));
99
103
  --color-surface-alt: var(--sf-colour-surface-alt, oklch(96.6% 0.018 82));
104
+ --color-surface-hover: var(--sf-colour-surface-hover, color-mix(in oklch, var(--color-surface-alt) 86%, var(--color-primary)));
105
+ --color-surface-raised-2: var(--sf-colour-surface-raised-2, color-mix(in oklch, var(--color-surface) 86%, white));
106
+ --color-categorical-1: var(--sf-colour-categorical-1, var(--color-primary));
107
+ --color-categorical-2: var(--sf-colour-categorical-2, var(--color-green));
108
+ --color-categorical-3: var(--sf-colour-categorical-3, var(--color-deco-blue));
109
+ --color-categorical-4: var(--sf-colour-categorical-4, var(--color-deco-lime));
110
+ --color-categorical-5: var(--sf-colour-categorical-5, var(--color-warm-icon));
111
+ --color-categorical-6: var(--sf-colour-categorical-6, var(--color-purple-icon));
100
112
  --color-hero-bg: oklch(8% 0.02 260);
101
113
  --color-green: var(--sf-colour-green, oklch(55% 0.12 148));
102
114
  --color-green-dark: var(--sf-colour-green-dark, oklch(45% 0.12 148));
@@ -168,6 +180,8 @@
168
180
  --color-surface: var(--sf-colour-surface);
169
181
  --sf-colour-surface-alt: oklch(96.4% 0.011 80);
170
182
  --color-surface-alt: var(--sf-colour-surface-alt);
183
+ --sf-colour-surface-elevated: var(--sf-colour-surface-raised);
184
+ --color-surface-elevated: var(--sf-colour-surface-elevated);
171
185
  --sf-colour-border: oklch(18% 0.026 250 / 0.12);
172
186
  --color-border: var(--sf-colour-border);
173
187
  --sf-colour-primary: oklch(68% 0.18 44);
@@ -19,6 +19,8 @@
19
19
  --step-6: clamp(2.986rem, 2.5863rem + 1.7763vw, 4.2915rem);
20
20
  --step-7: clamp(3.5832rem, 3.0379rem + 2.4235vw, 5.3644rem);
21
21
  --step-8: clamp(4.2998rem, 3.5634rem + 3.2731vw, 6.7055rem);
22
+ --type-caption-sm: var(--step--2);
23
+ --text-base: var(--step-0);
22
24
 
23
25
  --space-3xs: clamp(0.25rem, 0.2404rem + 0.0425vw, 0.2813rem);
24
26
  --space-2xs: clamp(0.5rem, 0.4809rem + 0.085vw, 0.5625rem);
@@ -88,7 +90,9 @@
88
90
  --color-destructive: var(--sf-colour-destructive, oklch(55% 0.2 28));
89
91
  --color-destructive-foreground: var(--sf-colour-destructive-foreground, oklch(98% 0.01 95));
90
92
  --color-border: var(--sf-colour-border, oklch(100% 0 0 / 0.12));
93
+ --color-border-subtle: var(--sf-colour-border-subtle, oklch(100% 0 0 / 0.08));
91
94
  --color-input: var(--sf-colour-input, oklch(100% 0 0 / 0.12));
95
+ --color-backdrop: var(--sf-colour-backdrop, oklch(0% 0 0 / 0.56));
92
96
  --color-ring: var(--sf-colour-ring, oklch(69% 0.18 44));
93
97
  --color-heading: var(--sf-colour-heading, oklch(20% 0.02 18));
94
98
  --color-page-text: var(--sf-colour-page-text, oklch(23% 0.02 18));
@@ -97,6 +101,14 @@
97
101
  --color-subdued: var(--sf-colour-subdued, oklch(42% 0.02 20));
98
102
  --color-surface: var(--sf-colour-surface, oklch(96% 0.015 80));
99
103
  --color-surface-alt: var(--sf-colour-surface-alt, oklch(96.6% 0.018 82));
104
+ --color-surface-hover: var(--sf-colour-surface-hover, color-mix(in oklch, var(--color-surface-alt) 86%, var(--color-primary)));
105
+ --color-surface-raised-2: var(--sf-colour-surface-raised-2, color-mix(in oklch, var(--color-surface) 86%, white));
106
+ --color-categorical-1: var(--sf-colour-categorical-1, var(--color-primary));
107
+ --color-categorical-2: var(--sf-colour-categorical-2, var(--color-green));
108
+ --color-categorical-3: var(--sf-colour-categorical-3, var(--color-deco-blue));
109
+ --color-categorical-4: var(--sf-colour-categorical-4, var(--color-deco-lime));
110
+ --color-categorical-5: var(--sf-colour-categorical-5, var(--color-warm-icon));
111
+ --color-categorical-6: var(--sf-colour-categorical-6, var(--color-purple-icon));
100
112
  --color-hero-bg: oklch(8% 0.02 260);
101
113
  --color-green: var(--sf-colour-green, oklch(55% 0.12 148));
102
114
  --color-green-dark: var(--sf-colour-green-dark, oklch(45% 0.12 148));
@@ -168,6 +180,8 @@
168
180
  --color-surface: var(--sf-colour-surface);
169
181
  --sf-colour-surface-alt: oklch(96.4% 0.011 80);
170
182
  --color-surface-alt: var(--sf-colour-surface-alt);
183
+ --sf-colour-surface-elevated: var(--sf-colour-surface-raised);
184
+ --color-surface-elevated: var(--sf-colour-surface-elevated);
171
185
  --sf-colour-border: oklch(18% 0.026 250 / 0.12);
172
186
  --color-border: var(--sf-colour-border);
173
187
  --sf-colour-primary: oklch(68% 0.18 44);
@@ -19,6 +19,8 @@
19
19
  --step-6: clamp(2.986rem, 2.5863rem + 1.7763vw, 4.2915rem);
20
20
  --step-7: clamp(3.5832rem, 3.0379rem + 2.4235vw, 5.3644rem);
21
21
  --step-8: clamp(4.2998rem, 3.5634rem + 3.2731vw, 6.7055rem);
22
+ --type-caption-sm: var(--step--2);
23
+ --text-base: var(--step-0);
22
24
 
23
25
  --space-3xs: clamp(0.25rem, 0.2404rem + 0.0425vw, 0.2813rem);
24
26
  --space-2xs: clamp(0.5rem, 0.4809rem + 0.085vw, 0.5625rem);
@@ -88,7 +90,9 @@
88
90
  --color-destructive: var(--sf-colour-destructive, oklch(55% 0.2 28));
89
91
  --color-destructive-foreground: var(--sf-colour-destructive-foreground, oklch(98% 0.01 95));
90
92
  --color-border: var(--sf-colour-border, oklch(100% 0 0 / 0.12));
93
+ --color-border-subtle: var(--sf-colour-border-subtle, oklch(100% 0 0 / 0.08));
91
94
  --color-input: var(--sf-colour-input, oklch(100% 0 0 / 0.12));
95
+ --color-backdrop: var(--sf-colour-backdrop, oklch(0% 0 0 / 0.56));
92
96
  --color-ring: var(--sf-colour-ring, oklch(69% 0.18 44));
93
97
  --color-heading: var(--sf-colour-heading, oklch(20% 0.02 18));
94
98
  --color-page-text: var(--sf-colour-page-text, oklch(23% 0.02 18));
@@ -97,6 +101,14 @@
97
101
  --color-subdued: var(--sf-colour-subdued, oklch(42% 0.02 20));
98
102
  --color-surface: var(--sf-colour-surface, oklch(96% 0.015 80));
99
103
  --color-surface-alt: var(--sf-colour-surface-alt, oklch(96.6% 0.018 82));
104
+ --color-surface-hover: var(--sf-colour-surface-hover, color-mix(in oklch, var(--color-surface-alt) 86%, var(--color-primary)));
105
+ --color-surface-raised-2: var(--sf-colour-surface-raised-2, color-mix(in oklch, var(--color-surface) 86%, white));
106
+ --color-categorical-1: var(--sf-colour-categorical-1, var(--color-primary));
107
+ --color-categorical-2: var(--sf-colour-categorical-2, var(--color-green));
108
+ --color-categorical-3: var(--sf-colour-categorical-3, var(--color-deco-blue));
109
+ --color-categorical-4: var(--sf-colour-categorical-4, var(--color-deco-lime));
110
+ --color-categorical-5: var(--sf-colour-categorical-5, var(--color-warm-icon));
111
+ --color-categorical-6: var(--sf-colour-categorical-6, var(--color-purple-icon));
100
112
  --color-hero-bg: oklch(8% 0.02 260);
101
113
  --color-green: var(--sf-colour-green, oklch(55% 0.12 148));
102
114
  --color-green-dark: var(--sf-colour-green-dark, oklch(45% 0.12 148));
@@ -168,6 +180,8 @@
168
180
  --color-surface: var(--sf-colour-surface);
169
181
  --sf-colour-surface-alt: oklch(96.4% 0.011 80);
170
182
  --color-surface-alt: var(--sf-colour-surface-alt);
183
+ --sf-colour-surface-elevated: var(--sf-colour-surface-raised);
184
+ --color-surface-elevated: var(--sf-colour-surface-elevated);
171
185
  --sf-colour-border: oklch(18% 0.026 250 / 0.12);
172
186
  --color-border: var(--sf-colour-border);
173
187
  --sf-colour-primary: oklch(68% 0.18 44);