@syncedco/flow 0.2.0 → 0.3.1

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/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);
@@ -885,6 +895,76 @@
885
895
  --sf-data-table-padding-inline: var(--sf-space-xs);
886
896
  }
887
897
 
898
+ .sf-data-table :where(th button, .sf-table-sort) {
899
+ align-items: center;
900
+ color: inherit;
901
+ display: inline-flex;
902
+ font: inherit;
903
+ font-weight: inherit;
904
+ gap: var(--sf-space-3xs);
905
+ padding: 0;
906
+ }
907
+
908
+ .sf-data-table :where(th button, .sf-table-sort)[aria-sort]::after {
909
+ color: var(--sf-colour-subtle);
910
+ content: "\2195";
911
+ font-size: var(--sf-step--2);
912
+ }
913
+
914
+ .sf-data-table :where(th button, .sf-table-sort)[aria-sort="ascending"]::after { content: "\2191"; }
915
+ .sf-data-table :where(th button, .sf-table-sort)[aria-sort="descending"]::after { content: "\2193"; }
916
+
917
+ .sf-data-table tr[data-clickable="true"] {
918
+ cursor: pointer;
919
+ }
920
+
921
+ .sf-data-table tr[data-clickable="true"]:focus-within {
922
+ outline: 0.125rem solid var(--sf-colour-ring);
923
+ outline-offset: -0.125rem;
924
+ }
925
+
926
+ @container (max-width: 38rem) {
927
+ .sf-table-wrap[data-view="cards"] {
928
+ border: 0;
929
+ overflow: visible;
930
+ }
931
+
932
+ .sf-table-wrap[data-view="cards"] .sf-data-table,
933
+ .sf-table-wrap[data-view="cards"] .sf-data-table :where(thead, tbody, tr, th, td) {
934
+ display: block;
935
+ min-inline-size: 0;
936
+ }
937
+
938
+ .sf-table-wrap[data-view="cards"] .sf-data-table thead {
939
+ display: none;
940
+ }
941
+
942
+ .sf-table-wrap[data-view="cards"] .sf-data-table tr {
943
+ background: var(--sf-colour-surface);
944
+ border: 1px solid var(--sf-colour-border);
945
+ border-radius: var(--sf-radius-panel);
946
+ margin-block-end: var(--sf-space-xs);
947
+ padding: var(--sf-space-xs);
948
+ }
949
+
950
+ .sf-table-wrap[data-view="cards"] .sf-data-table td {
951
+ align-items: center;
952
+ border: 0;
953
+ display: flex;
954
+ gap: var(--sf-space-s);
955
+ justify-content: space-between;
956
+ padding-block: var(--sf-space-2xs);
957
+ padding-inline: 0;
958
+ }
959
+
960
+ .sf-table-wrap[data-view="cards"] .sf-data-table td::before {
961
+ color: var(--sf-colour-muted);
962
+ content: attr(data-label);
963
+ font-size: var(--sf-step--1);
964
+ font-weight: 800;
965
+ }
966
+ }
967
+
888
968
  .sf-status {
889
969
  align-items: center;
890
970
  background: var(--sf-status-bg, var(--sf-colour-surface-alt));
@@ -900,6 +980,12 @@
900
980
  padding-inline: .7em;
901
981
  }
902
982
 
983
+ .sf-status[data-size="sm"] {
984
+ font-size: var(--sf-step--2);
985
+ min-block-size: 1.35rem;
986
+ padding-inline: .55em;
987
+ }
988
+
903
989
  .sf-status::before {
904
990
  background: currentColor;
905
991
  block-size: .42em;
@@ -908,6 +994,10 @@
908
994
  inline-size: .42em;
909
995
  }
910
996
 
997
+ :where(.sf-status[data-dot="false"], .sf-status--label)::before {
998
+ content: none;
999
+ }
1000
+
911
1001
  .sf-status[data-tone="success"] {
912
1002
  --sf-status-bg: var(--sf-colour-success-soft);
913
1003
  --sf-status-border: color-mix(in oklch, var(--sf-colour-success) 34%, transparent);
@@ -1055,6 +1145,13 @@
1055
1145
  gap: var(--sf-form-gap, var(--sf-space-s-m));
1056
1146
  }
1057
1147
 
1148
+ .sf-form[data-density="compact"],
1149
+ .sf-field[data-density="compact"] {
1150
+ --sf-form-gap: var(--sf-space-xs);
1151
+ --sf-input-block-size: 2.25rem;
1152
+ --sf-input-padding-inline: var(--sf-space-xs);
1153
+ }
1154
+
1058
1155
  .sf-fieldset {
1059
1156
  border: 1px solid var(--sf-colour-border);
1060
1157
  border-radius: var(--sf-radius-panel);
@@ -1120,6 +1217,45 @@
1120
1217
  transition: background-color var(--sf-duration-fast) var(--sf-ease-standard), border-color var(--sf-duration-fast) var(--sf-ease-standard), box-shadow var(--sf-duration-fast) var(--sf-ease-standard);
1121
1218
  }
1122
1219
 
1220
+ .sf-search {
1221
+ position: relative;
1222
+ }
1223
+
1224
+ .sf-search::before {
1225
+ block-size: .75em;
1226
+ border: 0.125em solid currentColor;
1227
+ border-radius: var(--sf-radius-full);
1228
+ color: var(--sf-colour-subtle);
1229
+ content: "";
1230
+ inline-size: .75em;
1231
+ inset-block-start: 50%;
1232
+ inset-inline-start: var(--sf-space-xs);
1233
+ line-height: 1;
1234
+ position: absolute;
1235
+ transform: translateY(-58%);
1236
+ }
1237
+
1238
+ .sf-search::after {
1239
+ background: var(--sf-colour-subtle);
1240
+ block-size: .125em;
1241
+ content: "";
1242
+ inline-size: .45em;
1243
+ inset-block-start: 50%;
1244
+ inset-inline-start: calc(var(--sf-space-xs) + .58em);
1245
+ position: absolute;
1246
+ transform: translateY(120%) rotate(45deg);
1247
+ transform-origin: inline-start center;
1248
+ }
1249
+
1250
+ .sf-search > .sf-input {
1251
+ padding-inline-start: var(--sf-space-l);
1252
+ }
1253
+
1254
+ .sf-help[data-density="compact"],
1255
+ .sf-error[data-density="compact"] {
1256
+ font-size: var(--sf-step--2);
1257
+ }
1258
+
1123
1259
  .sf-textarea {
1124
1260
  min-block-size: var(--sf-textarea-block-size, 8rem);
1125
1261
  padding-block: var(--sf-space-xs);
@@ -1240,7 +1376,7 @@
1240
1376
  .sf-dialog::backdrop,
1241
1377
  .sf-popover::backdrop,
1242
1378
  .sf-drawer::backdrop {
1243
- background: oklch(0% 0 0 / 0.48);
1379
+ background: var(--sf-colour-backdrop);
1244
1380
  }
1245
1381
 
1246
1382
  .sf-dialog__header,
@@ -1266,6 +1402,11 @@
1266
1402
  justify-content: flex-end;
1267
1403
  }
1268
1404
 
1405
+ .sf-dialog[data-layout="form"] .sf-dialog__body {
1406
+ display: grid;
1407
+ gap: var(--sf-space-s);
1408
+ }
1409
+
1269
1410
  .sf-popover,
1270
1411
  .sf-menu-popover,
1271
1412
  .sf-tooltip,
@@ -1291,6 +1432,7 @@
1291
1432
  :where(.sf-popover, .sf-menu-popover, .sf-tooltip, .sf-toast, .sf-banner, .sf-drawer):popover-open {
1292
1433
  display: grid;
1293
1434
  gap: var(--sf-space-xs);
1435
+ z-index: var(--sf-z-overlay);
1294
1436
  }
1295
1437
 
1296
1438
  .sf-popover {
@@ -1319,6 +1461,16 @@
1319
1461
  background: var(--sf-colour-surface-alt);
1320
1462
  }
1321
1463
 
1464
+ .sf-menu :where(a, button)[data-variant="danger"],
1465
+ .sf-menu-popover :where(a, button)[data-variant="danger"] {
1466
+ color: var(--sf-colour-danger);
1467
+ }
1468
+
1469
+ .sf-menu :where(a, button)[data-variant="danger"]:hover,
1470
+ .sf-menu-popover :where(a, button)[data-variant="danger"]:hover {
1471
+ background: var(--sf-colour-danger-soft);
1472
+ }
1473
+
1322
1474
  .sf-tooltip {
1323
1475
  border-radius: var(--sf-radius-md);
1324
1476
  box-shadow: var(--sf-shadow-md);
@@ -1345,6 +1497,16 @@
1345
1497
  top: var(--sf-space-s);
1346
1498
  }
1347
1499
 
1500
+ .sf-toast-stack {
1501
+ display: grid;
1502
+ gap: var(--sf-space-xs);
1503
+ inline-size: min(100% - (var(--sf-gutter) * 2), var(--sf-toast-width, 24rem));
1504
+ inset-block-start: var(--sf-space-s);
1505
+ inset-inline-end: var(--sf-space-s);
1506
+ position: fixed;
1507
+ z-index: var(--sf-z-toast);
1508
+ }
1509
+
1348
1510
  .sf-banner {
1349
1511
  block-size: auto;
1350
1512
  border-radius: var(--sf-radius-lg);
@@ -1457,15 +1619,23 @@
1457
1619
  }
1458
1620
 
1459
1621
  .sf-tab {
1622
+ align-items: center;
1460
1623
  border-block-end: 0.125rem solid transparent;
1624
+ border-radius: var(--sf-radius-full);
1461
1625
  color: var(--sf-colour-muted);
1462
1626
  display: inline-flex;
1627
+ gap: var(--sf-space-2xs);
1463
1628
  font-weight: 800;
1464
1629
  padding-block: var(--sf-space-xs);
1465
1630
  padding-inline: var(--sf-space-s);
1466
1631
  text-decoration: none;
1467
1632
  }
1468
1633
 
1634
+ .sf-tab[data-variant="pill"] {
1635
+ border: 1px solid transparent;
1636
+ padding-block: var(--sf-space-2xs);
1637
+ }
1638
+
1469
1639
  .sf-tab:hover,
1470
1640
  .sf-tab[aria-selected="true"],
1471
1641
  .sf-tab[data-state="active"] {
@@ -1473,6 +1643,24 @@
1473
1643
  color: var(--sf-colour-foreground);
1474
1644
  }
1475
1645
 
1646
+ .sf-tab[data-variant="pill"]:hover,
1647
+ .sf-tab[data-variant="pill"][aria-selected="true"],
1648
+ .sf-tab[data-variant="pill"][data-state="active"] {
1649
+ background: var(--sf-colour-primary-soft);
1650
+ border-color: var(--sf-colour-primary-soft-border);
1651
+ color: var(--sf-colour-primary);
1652
+ }
1653
+
1654
+ .sf-tab__count {
1655
+ background: var(--sf-colour-surface-alt);
1656
+ border: 1px solid var(--sf-colour-border);
1657
+ border-radius: var(--sf-radius-full);
1658
+ color: var(--sf-colour-muted);
1659
+ font-size: var(--sf-step--2);
1660
+ line-height: 1;
1661
+ padding: .25em .55em;
1662
+ }
1663
+
1476
1664
  .sf-tab-panel {
1477
1665
  background: var(--sf-colour-surface);
1478
1666
  border: 1px solid var(--sf-colour-border);
@@ -13,9 +13,9 @@ These are intended for application code and examples.
13
13
  | Imports | `@syncedco/flow/styles.css`, `tokens.css`, `reset.css`, `base.css`, `defaults.css`, `layout.css`, `components.css`, `utilities.css` |
14
14
  | Tokens | Unlayered `--sf-*` custom properties emitted by `tokens.css` |
15
15
  | Theme config | `theme.fonts`, `theme.colours`, `theme.darkColours`, `theme.radii`, `theme.layout`, `theme.components` |
16
- | Layout classes | `sf-container`, `sf-section`, `sf-stack`, `sf-flow`, `sf-cluster`, `sf-repel`, `sf-toolbar`, `sf-app-shell`, `sf-app-sidebar`, `sf-app-main`, `sf-auto-grid`, `sf-switcher`, `sf-sidebar`, `sf-split`, `sf-frame`, `sf-cover`, `sf-metric-grid`, `sf-pipeline` |
17
- | Components | `sf-button`, `sf-icon`, `sf-icon-button`, `sf-avatar`, `sf-chart`, `sf-meter`, `sf-progress`, `sf-skeleton`, `sf-filter-bar`, `sf-data-table`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel`, `sf-card`, `sf-surface`, `sf-hero`, `sf-nav`, `sf-form`, `sf-field`, `sf-input`, `sf-select`, `sf-textarea`, `sf-check`, `sf-alert`, `sf-badge`, `sf-section-header`, `sf-kicker` |
18
- | Native components | `sf-dialog`, `sf-popover`, `sf-tooltip`, `sf-drawer`, `sf-drawer--stack`, `sf-disclosure`, `sf-accordion`, `sf-tabs`, `sf-menu`, `sf-breadcrumb`, `sf-pagination` |
16
+ | Layout classes | `sf-container`, `sf-section`, `sf-stack`, `sf-flow`, `sf-cluster`, `sf-repel`, `sf-toolbar`, `sf-app-shell`, `sf-app-header`, `sf-app-sidebar`, `sf-app-backdrop`, `sf-app-main`, `sf-auto-grid`, `sf-switcher`, `sf-sidebar`, `sf-split`, `sf-frame`, `sf-cover`, `sf-metric-grid`, `sf-pipeline` |
17
+ | Components | `sf-button`, `sf-icon`, `sf-icon-button`, `sf-avatar`, `sf-chart`, `sf-meter`, `sf-progress`, `sf-skeleton`, `sf-filter-bar`, `sf-search`, `sf-data-table`, `sf-table-sort`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel`, `sf-card`, `sf-surface`, `sf-hero`, `sf-nav`, `sf-form`, `sf-field`, `sf-input`, `sf-select`, `sf-textarea`, `sf-check`, `sf-alert`, `sf-badge`, `sf-section-header`, `sf-kicker` |
18
+ | Native components | `sf-dialog`, `sf-popover`, `sf-tooltip`, `sf-drawer`, `sf-drawer--stack`, `sf-disclosure`, `sf-accordion`, `sf-tabs`, `sf-tab__count`, `sf-menu`, `sf-breadcrumb`, `sf-pagination`, `sf-toast-stack` |
19
19
  | Website patterns | `sf-logo-cloud`, `sf-feature`, `sf-stats`, `sf-testimonial`, `sf-pricing-grid`, `sf-price-card`, `sf-faq`, `sf-cta`, `sf-footer` |
20
20
  | Utilities | `sf-prose`, `sf-link`, `sf-link-subtle`, `sf-link-plain`, `sf-list-*`, `sf-push-*`, `sf-focus-ring`, `sf-touch-target`, `sf-skip-link`, `sr-only`, `not-sr-only` |
21
21
  | CLI | `synced-flow init`, `agents install`, `agents status`, `skill`, `add defaults`, `build`, `watch`, `lint`, `doctor`, `tokens`, `catalog`, `suggest`, `pattern`, `recipe`, `theme init`, `theme validate` |
@@ -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.
@@ -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.
package/docs/patterns.md CHANGED
@@ -18,6 +18,13 @@ pnpm exec synced-flow pattern scroll-viewport-sections --json
18
18
  | `scroll-viewport-with-spy` | Scroll snap panels with optional IntersectionObserver active-state notes. |
19
19
  | `native-dialog-react` | Native `<dialog>` markup plus React/Next fallback shape. |
20
20
  | `popover-drawer-layout` | Popover-backed drawer or filter panel with `sf-drawer--stack`. |
21
+ | `app-shell-layout` | Fixed header, sidebar, collapsed state, and mobile drawer app shell. |
22
+ | `row-action-menu` | Native popover row actions with destructive menu item styling. |
23
+ | `tabs-with-counts` | Pill tabs with compact count chips. |
24
+ | `data-table-actions` | Sortable header controls, clickable rows, and responsive card fallback. |
25
+ | `search-field` | Labelled native search input with icon shell and compact density. |
26
+ | `modal-form` | Native dialog layout for form body and footer actions. |
27
+ | `toast-stack` | Fixed app notification stack layout. |
21
28
  | `filter-toolbar` | Search, filters, and actions above app lists and tables. |
22
29
  | `date-filter-form` | Native date/time inputs inside filter forms. |
23
30
  | `active-filter-tags` | Visible selected filter tags with remove and clear actions. |
@@ -14,7 +14,10 @@ Use the `--sf-*` variables as the stable CSS foundation.
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
+ | 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
+ | Categorical colour | `--sf-colour-categorical-1` ... `--sf-colour-categorical-6` | Charts, stages, labels, and recurring data accents. |
17
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. |
18
21
  | Motion | `--sf-duration-fast`, `--sf-duration-normal`, `--sf-ease-standard` | Consistent transitions. |
19
22
  | Component | `--sf-button-*`, `--sf-card-*`, `--sf-input-*`, `--sf-alert-*` | Reusable component defaults. |
20
23
  | Accessibility | `:focus-visible`, `:target`, `[aria-invalid]`, `[aria-current]`, `[aria-expanded]`, `[aria-selected]`, `[aria-busy]`, `[aria-disabled]` | Visible native and ARIA states. |
@@ -36,7 +39,7 @@ These classes are the first choice for page structure.
36
39
  | `sf-cluster` | Wrapping inline groups such as actions or tags. |
37
40
  | `sf-repel` | Space-between layout that wraps safely. |
38
41
  | `sf-toolbar` | Dense app/page toolbar that wraps actions safely. |
39
- | `sf-app-shell`, `sf-app-sidebar`, `sf-app-sidebar__brand`, `sf-app-main` | Product app shell with persistent sidebar and main workspace. |
42
+ | `sf-app-shell`, `sf-app-header`, `sf-app-sidebar`, `sf-app-sidebar__brand`, `sf-app-backdrop`, `sf-app-main` | Product app shell with optional fixed header, collapsed sidebar, mobile drawer, and main workspace. |
40
43
  | `sf-split` | Responsive two-column layout. |
41
44
  | `sf-split--reverse` | Reverses split layout visual order. |
42
45
  | `sf-auto-grid`, `sf-panel-grid`, `sf-metric-grid`, `sf-pipeline` | Responsive card, metric, and operational grids. |
@@ -62,16 +65,16 @@ These cover the common UI elements needed for a simple site.
62
65
  | `sf-nav`, `sf-nav__list`, `sf-nav__link` | Navigation basics. |
63
66
  | `sf-nav--mobile`, `sf-menu`, `sf-breadcrumb`, `sf-pagination` | Mobile, menu, breadcrumb, and paginated navigation. |
64
67
  | `sf-dialog`, `sf-dialog__header`, `sf-dialog__body`, `sf-dialog__footer` | Native `<dialog>` styling. |
65
- | `sf-popover`, `sf-tooltip`, `sf-tooltip-trigger`, `sf-menu-popover`, `sf-toast`, `sf-banner`, `sf-drawer` | Popover-backed native overlays. |
68
+ | `sf-popover`, `sf-tooltip`, `sf-tooltip-trigger`, `sf-menu-popover`, `sf-toast`, `sf-toast-stack`, `sf-banner`, `sf-drawer` | Popover-backed native overlays and app feedback stacks. |
66
69
  | `sf-disclosure`, `sf-accordion` | Native `details`/`summary` disclosure patterns. |
67
- | `sf-tabs`, `sf-tab-list`, `sf-tab`, `sf-tab-panel` | HTML/CSS-first tab styling. |
70
+ | `sf-tabs`, `sf-tab-list`, `sf-tab`, `sf-tab__count`, `sf-tab-panel` | HTML/CSS-first tab styling, including counted pill tabs. |
68
71
  | `sf-form`, `sf-fieldset`, `sf-field`, `sf-label`, `sf-help`, `sf-error` | Form structure and messaging. |
69
- | `sf-input`, `sf-select`, `sf-textarea`, `sf-check` | Form controls. |
72
+ | `sf-input`, `sf-select`, `sf-textarea`, `sf-search`, `sf-check` | Form controls, including search input shell. |
70
73
  | `sf-alert`, `sf-alert--info`, `sf-alert--success`, `sf-alert--warning`, `sf-alert--danger`, `sf-alert__title` | Notices and feedback. |
71
74
  | `sf-section-header`, `sf-kicker`, `sf-badge`, `sf-avatar` | Common marketing/content and account patterns. |
72
75
  | `sf-chart`, `sf-chart__plot`, `sf-chart__svg`, `sf-chart__legend`, `sf-meter-list`, `sf-meter` | Lightweight chart shells for app-owned SVG charts and native meter bars. |
73
76
  | `sf-progress`, `sf-skeleton` | Native progress bars and CSS-only loading placeholders for async app states. |
74
- | `sf-filter-bar`, `sf-data-table`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel` | Lean application primitives for filters, operational data, status, empty states, and settings/detail screens. |
77
+ | `sf-filter-bar`, `sf-data-table`, `sf-table-sort`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel` | Lean application primitives for filters, operational data, status, empty states, and settings/detail screens. |
75
78
 
76
79
  ## Utility Classes
77
80
 
package/docs/tokens.md CHANGED
@@ -75,6 +75,7 @@ The generated core includes enough semantic variables for common website UI:
75
75
 
76
76
  - surfaces: `--sf-colour-background`, `--sf-colour-surface`,
77
77
  `--sf-colour-surface-alt`, `--sf-colour-surface-raised`,
78
+ `--sf-colour-surface-raised-2`, `--sf-colour-surface-hover`,
78
79
  `--sf-colour-surface-inset`
79
80
  - text and links: `--sf-colour-foreground`, `--sf-colour-muted`,
80
81
  `--sf-colour-subtle`, `--sf-colour-link`, `--sf-colour-link-hover`
@@ -82,8 +83,13 @@ The generated core includes enough semantic variables for common website UI:
82
83
  `--sf-colour-primary-foreground`, `--sf-colour-primary-soft`
83
84
  - state feedback: `--sf-colour-success`, `--sf-colour-warning`,
84
85
  `--sf-colour-danger`, `--sf-colour-info`, plus matching soft variants
85
- - structure: `--sf-colour-border`, `--sf-colour-border-strong`,
86
+ - structure: `--sf-colour-border-subtle`, `--sf-colour-border`,
87
+ `--sf-colour-border-strong`, `--sf-colour-backdrop`,
86
88
  `--sf-radius-control`, `--sf-radius-panel`, `--sf-shadow-*`
89
+ - stacking: `--sf-z-sticky`, `--sf-z-header`, `--sf-z-backdrop`,
90
+ `--sf-z-drawer`, `--sf-z-overlay`, `--sf-z-toast`, `--sf-z-skip-link`
91
+ - data accents: `--sf-colour-categorical-1` through
92
+ `--sf-colour-categorical-6`
87
93
 
88
94
  For class-level usage, see [System primitives](system-primitives.md).
89
95
 
@@ -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
 
package/layout.css CHANGED
@@ -95,11 +95,47 @@
95
95
  }
96
96
 
97
97
  .sf-app-shell {
98
+ --sf-app-header-height: 4rem;
99
+ --sf-app-sidebar-collapsed-width: 4.75rem;
98
100
  background: var(--sf-colour-background);
99
101
  color: var(--sf-colour-foreground);
100
102
  display: grid;
101
103
  grid-template-columns: minmax(14rem, var(--sf-app-sidebar-width, 16rem)) minmax(0, 1fr);
102
104
  min-block-size: 100dvh;
105
+ position: relative;
106
+ transition: grid-template-columns var(--sf-duration-normal) var(--sf-ease-standard);
107
+ }
108
+
109
+ .sf-app-header {
110
+ align-items: center;
111
+ background: var(--sf-colour-surface);
112
+ border-block-end: 1px solid var(--sf-colour-border);
113
+ display: flex;
114
+ gap: var(--sf-space-s);
115
+ grid-column: 1 / -1;
116
+ justify-content: space-between;
117
+ min-block-size: var(--sf-app-header-height);
118
+ padding-inline: var(--sf-space-s-m);
119
+ z-index: var(--sf-z-header);
120
+ }
121
+
122
+ .sf-app-shell[data-layout~="fixed-header"] {
123
+ grid-template-rows: var(--sf-app-header-height) minmax(0, 1fr);
124
+ }
125
+
126
+ .sf-app-shell[data-layout~="fixed-header"] .sf-app-header {
127
+ inset-block-start: 0;
128
+ position: sticky;
129
+ }
130
+
131
+ .sf-app-shell[data-layout~="fixed-header"] .sf-app-sidebar {
132
+ inset-block-start: var(--sf-app-header-height);
133
+ max-block-size: calc(100dvh - var(--sf-app-header-height));
134
+ min-block-size: calc(100dvh - var(--sf-app-header-height));
135
+ }
136
+
137
+ .sf-app-shell[data-collapsed="true"] {
138
+ grid-template-columns: var(--sf-app-sidebar-collapsed-width) minmax(0, 1fr);
103
139
  }
104
140
 
105
141
  .sf-app-sidebar {
@@ -115,6 +151,7 @@
115
151
  padding: var(--sf-space-s);
116
152
  position: sticky;
117
153
  inset-block-start: 0;
154
+ transition: transform var(--sf-duration-normal) var(--sf-ease-standard), inline-size var(--sf-duration-normal) var(--sf-ease-standard);
118
155
  }
119
156
 
120
157
  .sf-app-sidebar__brand {
@@ -134,6 +171,59 @@
134
171
  padding: var(--sf-app-main-padding, var(--sf-space-m-l));
135
172
  }
136
173
 
174
+ .sf-app-shell[data-collapsed="true"] .sf-app-sidebar {
175
+ overflow-x: hidden;
176
+ }
177
+
178
+ .sf-app-shell[data-collapsed="true"] .sf-app-sidebar__brand,
179
+ .sf-app-shell[data-collapsed="true"] .sf-app-sidebar .sf-nav__link {
180
+ justify-content: center;
181
+ }
182
+
183
+ .sf-app-shell[data-collapsed="true"] [data-hide-collapsed="true"] {
184
+ display: none;
185
+ }
186
+
187
+ .sf-app-backdrop {
188
+ display: none;
189
+ }
190
+
191
+ @media (max-width: 48rem) {
192
+ .sf-app-shell[data-mobile="drawer"] {
193
+ display: block;
194
+ }
195
+
196
+ .sf-app-shell[data-mobile="drawer"] .sf-app-header {
197
+ position: sticky;
198
+ inset-block-start: 0;
199
+ }
200
+
201
+ .sf-app-shell[data-mobile="drawer"] .sf-app-sidebar {
202
+ block-size: 100dvh;
203
+ box-shadow: var(--sf-shadow-xl);
204
+ inline-size: min(18rem, calc(100vw - var(--sf-space-l)));
205
+ inset-block: 0;
206
+ inset-inline-start: 0;
207
+ max-block-size: none;
208
+ min-block-size: 100dvh;
209
+ position: fixed;
210
+ transform: translateX(-100%);
211
+ z-index: var(--sf-z-drawer);
212
+ }
213
+
214
+ .sf-app-shell[data-mobile="drawer"][data-sidebar-open="true"] .sf-app-sidebar {
215
+ transform: translateX(0);
216
+ }
217
+
218
+ .sf-app-shell[data-mobile="drawer"][data-sidebar-open="true"] .sf-app-backdrop {
219
+ background: var(--sf-colour-backdrop);
220
+ display: block;
221
+ inset: 0;
222
+ position: fixed;
223
+ z-index: var(--sf-z-backdrop);
224
+ }
225
+ }
226
+
137
227
  .sf-grid {
138
228
  display: grid;
139
229
  gap: var(--sf-grid-gap, var(--sf-gutter));
@@ -274,7 +364,7 @@
274
364
  .sf-sticky-top {
275
365
  position: sticky;
276
366
  inset-block-start: var(--sf-sticky-offset, 0);
277
- z-index: var(--sf-sticky-z, 20);
367
+ z-index: var(--sf-sticky-z, var(--sf-z-sticky));
278
368
  }
279
369
 
280
370
  .sf-media-object {