@kerfjs/ui 5.0.0-beta.35 → 5.0.0-beta.37

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.
Files changed (59) hide show
  1. package/README.md +11 -4
  2. package/ai/component-catalog-v2.json +90 -4
  3. package/ai/component-catalog.json +123 -17
  4. package/ai/public-api-signatures-v1.md +52 -13
  5. package/ai/skill.md +3 -3
  6. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  7. package/dist/browser/spacer.js +3 -0
  8. package/dist/catalog.js +2 -2
  9. package/dist/{chunk-MFR2IIFG.js → chunk-2TYUFZUC.js} +3 -1
  10. package/dist/chunk-2TYUFZUC.js.map +1 -0
  11. package/dist/{chunk-OC275HHS.js → chunk-5FJ53EOF.js} +5 -1
  12. package/dist/chunk-5FJ53EOF.js.map +1 -0
  13. package/dist/{chunk-7JQAQWVC.js → chunk-C34NZOLJ.js} +5 -1
  14. package/dist/chunk-C34NZOLJ.js.map +1 -0
  15. package/dist/{chunk-KFUUFNTS.js → chunk-EYALJQQD.js} +12 -2
  16. package/dist/chunk-EYALJQQD.js.map +1 -0
  17. package/dist/chunk-OGTJNHRG.js +42 -0
  18. package/dist/chunk-OGTJNHRG.js.map +1 -0
  19. package/dist/{chunk-QHCBZTDK.js → chunk-X5GRDQ7V.js} +5 -1
  20. package/dist/chunk-X5GRDQ7V.js.map +1 -0
  21. package/dist/divider-sides-BzB6rphT.d.ts +6 -0
  22. package/dist/index.d.ts +2 -1
  23. package/dist/index.js +6 -5
  24. package/dist/list-header.d.ts +4 -1
  25. package/dist/list-header.js +1 -1
  26. package/dist/list-inset-control.d.ts +5 -2
  27. package/dist/list-inset-control.js +1 -1
  28. package/dist/list-inset-text.d.ts +6 -3
  29. package/dist/list-inset-text.js +1 -1
  30. package/dist/list.d.ts +7 -3
  31. package/dist/list.js +1 -1
  32. package/dist/row.d.ts +7 -2
  33. package/dist/row.js +1 -1
  34. package/dist/spacer.d.ts +16 -0
  35. package/dist/spacer.js +4 -0
  36. package/dist/spacer.js.map +1 -0
  37. package/dist/styles/list-header.css +27 -0
  38. package/dist/styles/list-inset-control.css +17 -1
  39. package/dist/styles/list-inset-text.css +27 -11
  40. package/dist/styles/list.css +46 -0
  41. package/dist/styles/row.css +46 -0
  42. package/dist/styles/spacer.css +11 -0
  43. package/dist/styles/styles.css +1 -0
  44. package/dist/toolbar.d.ts +1 -1
  45. package/docs/component-contract.md +6 -3
  46. package/docs/component-selection.md +6 -3
  47. package/docs/design/templates/list-header/inline-action-dark.svg +1 -0
  48. package/docs/design/templates/list-header/inline-action.svg +1 -0
  49. package/docs/design/templates/list-header-dark.svg +1 -1
  50. package/docs/design/templates/list-header.svg +1 -1
  51. package/docs/layout.md +38 -0
  52. package/llms.txt +6 -1
  53. package/package.json +7 -1
  54. package/dist/chunk-7JQAQWVC.js.map +0 -1
  55. package/dist/chunk-KFUUFNTS.js.map +0 -1
  56. package/dist/chunk-MFR2IIFG.js.map +0 -1
  57. package/dist/chunk-OC275HHS.js.map +0 -1
  58. package/dist/chunk-QHCBZTDK.js.map +0 -1
  59. package/dist/divider-sides-267FA7sY.d.ts +0 -4
package/README.md CHANGED
@@ -126,7 +126,7 @@ Do not concatenate a count into `label` or pass a numeric `badge`; the mutually
126
126
  exclusive legacy `badge` slot remains available for non-count `SafeHtml` such
127
127
  as a `New` marker.
128
128
 
129
- `Row.gap` and `List.gap` accept a direct spacing name such as
129
+ `Row.gap`, `List.gap`, and `Spacer` dimensions accept a direct spacing name such as
130
130
  `gap="xs"` or `gap="m"`, or a branded `CssLength`. Import `space`, `rem`, `em`,
131
131
  `px`, `pct`, `lengthVar`, `plus`, and `calc` from the CSS-free
132
132
  `@kerfjs/ui/css-values` subpath when a named step is not enough. `plus` returns
@@ -138,7 +138,12 @@ Use `Row` for horizontal flex composition and `List` for vertical composition.
138
138
  Both accept `hAlign` (`left`, `center`, `right`, `full`) and `vAlign` (`top`,
139
139
  `middle`, `bottom`, `full`), plus their documented short/CSS aliases. `Row`
140
140
  defaults to left/full alignment, the `xs` gap, and `wrap={false}`; List keeps
141
- its existing full/top alignment and zero-gap defaults.
141
+ its existing full/top alignment and zero-gap defaults. Set `textInsets` or
142
+ `controlInsets` with canonical physical sides in `t`/`r`/`b`/`l` order (for
143
+ example, `textInsets="tbl"` or `controlInsets="r"`). Text geometry takes
144
+ precedence where both select the same side. `ListInsetText` and
145
+ `ListInsetControl` inset all four sides by default and accept the same `sides`
146
+ grammar for selective insets.
142
147
 
143
148
  The same property-specific boundary applies beyond spacing: use `flex()` (or a
144
149
  finite keyword) for `Row.flex` and `List.flex`; length builders and intrinsic-size keywords for
@@ -160,6 +165,7 @@ interchangeable. `ListItem` and `ListActionRow` deliberately expose no raw
160
165
  | `ToolbarText` | `@kerfjs/ui/toolbar-text` | `@kerfjs/ui/toolbar-text.css` |
161
166
  | `List` | `@kerfjs/ui/list` | `@kerfjs/ui/list.css` |
162
167
  | `Row` | `@kerfjs/ui/row` | `@kerfjs/ui/row.css` |
168
+ | `Spacer` | `@kerfjs/ui/spacer` | `@kerfjs/ui/spacer.css` |
163
169
  | Typed CSS dimension builders | `@kerfjs/ui/css-values` | — |
164
170
  | `ListActionRow` | `@kerfjs/ui/list-action-row` | `@kerfjs/ui/list-action-row.css` |
165
171
  | `ListItem` | `@kerfjs/ui/list-item` | `@kerfjs/ui/list-item.css` |
@@ -221,8 +227,9 @@ transform is applied. Direction changes take the shortest rotation path; a
221
227
  real controlled content and preserve the button's stable label; ordinary
222
228
  navigation must not display a disclosure arrow. A custom `actionIcon` remains
223
229
  an escape hatch and replaces the default arrow entirely. The header fills its
224
- available inline width, keeping a separate action at the logical end in a 44px
225
- target. Its visible action glyph defaults to 18px through
230
+ available inline width by default; set `inline` to shrink-wrap it beside other
231
+ content and remove its root margin, border, and padding. It keeps a separate
232
+ action at the logical end. Its visible action glyph defaults to 18px through
226
233
  `--kui-list-header-action-icon-size`.
227
234
 
228
235
  ## Machine-readable catalog
@@ -1436,9 +1436,9 @@
1436
1436
  "geometry": {
1437
1437
  "margin": "none",
1438
1438
  "border": "none",
1439
- "padding": "none",
1439
+ "padding": "conditional",
1440
1440
  "notes": [
1441
- "Row adds no margin, border, or padding; it owns only flex participation, alignment, wrapping, and the gap between children."
1441
+ "Row adds no margin or border. textInsets adds the full 17px content inset and controlInsets adds 8px on selected physical sides; text wins when both select a side."
1442
1442
  ]
1443
1443
  }
1444
1444
  },
@@ -1486,6 +1486,92 @@
1486
1486
  "composition": "docs/layout.md#row-and-list-alignment"
1487
1487
  }
1488
1488
  },
1489
+ {
1490
+ "key": "@kerfjs/ui:spacer",
1491
+ "package": "@kerfjs/ui",
1492
+ "id": "spacer",
1493
+ "name": "Spacer",
1494
+ "kind": "component",
1495
+ "source": "kerf",
1496
+ "parents": { "mode": "any", "entries": [] },
1497
+ "contexts": ["application-ui"],
1498
+ "zones": [],
1499
+ "children": { "mode": "any", "concepts": [], "requiredConcepts": [] },
1500
+ "state": [
1501
+ {
1502
+ "id": "whether the empty space carries structural meaning",
1503
+ "owner": "application",
1504
+ "required": false
1505
+ },
1506
+ {
1507
+ "id": "the named or typed dimensions",
1508
+ "owner": "application",
1509
+ "required": false
1510
+ },
1511
+ {
1512
+ "id": "whether the spacer fills the flex main axis",
1513
+ "owner": "application",
1514
+ "required": false
1515
+ }
1516
+ ],
1517
+ "wiring": { "required": false, "helpers": [], "obligations": [] },
1518
+ "responsive": { "owner": "not-applicable", "behaviors": [] },
1519
+ "layout": {
1520
+ "roles": ["structure"],
1521
+ "geometry": {
1522
+ "margin": "none",
1523
+ "border": "none",
1524
+ "padding": "none",
1525
+ "notes": [
1526
+ "A fixed Spacer does not shrink. flex changes its flex shorthand from 0 0 auto to 1 1 auto; width and height remain physical dimensions."
1527
+ ]
1528
+ }
1529
+ },
1530
+ "accessibility": {
1531
+ "obligations": [
1532
+ "Spacer is always decorative and hidden from the accessibility tree.",
1533
+ "Spacer must not contain content or become an interaction target."
1534
+ ]
1535
+ },
1536
+ "cssValueProps": [
1537
+ {
1538
+ "path": "width",
1539
+ "grammar": "length",
1540
+ "helpers": ["px", "rem", "em", "pct", "space", "lengthVar", "calc"],
1541
+ "nonStandaloneHelpers": ["plus"],
1542
+ "shorthands": ["none", "2xs", "xs", "s", "m", "l", "xl"],
1543
+ "canonicalShorthands": ["none", "2xs", "xs", "m", "l"],
1544
+ "exceptionalShorthands": ["s", "xl"],
1545
+ "rawPolicy": "forbid",
1546
+ "examples": [
1547
+ "width=\"m\"",
1548
+ "width={space(\"m\")}",
1549
+ "width={calc(plus(rem(0.25), pct(10)))}"
1550
+ ]
1551
+ },
1552
+ {
1553
+ "path": "height",
1554
+ "grammar": "length",
1555
+ "helpers": ["px", "rem", "em", "pct", "space", "lengthVar", "calc"],
1556
+ "nonStandaloneHelpers": ["plus"],
1557
+ "shorthands": ["none", "2xs", "xs", "s", "m", "l", "xl"],
1558
+ "canonicalShorthands": ["none", "2xs", "xs", "m", "l"],
1559
+ "exceptionalShorthands": ["s", "xl"],
1560
+ "rawPolicy": "forbid",
1561
+ "examples": ["height=\"xs\"", "height={rem(1)}"]
1562
+ }
1563
+ ],
1564
+ "boundaries": {
1565
+ "rootClass": "kui-spacer",
1566
+ "publicClasses": ["kui-spacer"],
1567
+ "publicTokens": []
1568
+ },
1569
+ "diagnostics": [],
1570
+ "provenance": {
1571
+ "selection": "ai/component-catalog.json",
1572
+ "composition": "generated-permissive-default"
1573
+ }
1574
+ },
1489
1575
  {
1490
1576
  "key": "@kerfjs/ui:list",
1491
1577
  "package": "@kerfjs/ui",
@@ -1521,9 +1607,9 @@
1521
1607
  "geometry": {
1522
1608
  "margin": "none",
1523
1609
  "border": "conditional",
1524
- "padding": "none",
1610
+ "padding": "conditional",
1525
1611
  "notes": [
1526
- "List adds no margin or padding; dividerSides draws only the selected physical edges without changing box geometry."
1612
+ "List adds no margin. dividerSides draws selected physical edges without changing geometry; textInsets adds the full 17px content inset and controlInsets adds 8px on selected sides, with text winning on overlap."
1527
1613
  ]
1528
1614
  }
1529
1615
  },
@@ -1642,9 +1642,9 @@
1642
1642
  "category": "Structure",
1643
1643
  "kind": "component",
1644
1644
  "source": "kerf",
1645
- "description": "A horizontal flex row with physical-axis alignment, a typed xs gap by default, flex participation, and opt-in wrapping.",
1645
+ "description": "A horizontal flex row with physical-axis alignment, side-selectable text/control insets, a typed xs gap by default, flex participation, and opt-in wrapping.",
1646
1646
  "uses": [],
1647
- "purpose": "Arrange related content horizontally with consistent spacing and explicit left/center/right/full plus top/middle/bottom/full alignment instead of application-owned flex wrappers.",
1647
+ "purpose": "Arrange related content horizontally with consistent spacing, optional physical-side text/control insets, and explicit left/center/right/full plus top/middle/bottom/full alignment instead of application-owned flex wrappers.",
1648
1648
  "publicExports": ["Row"],
1649
1649
  "cssValueProps": [
1650
1650
  {
@@ -1674,6 +1674,7 @@
1674
1674
  "Choose physical horizontal distribution and vertical alignment without writing one-off flex CSS.",
1675
1675
  "Use a finite flex keyword or the flex() builder when Row participates in a flexible parent layout.",
1676
1676
  "Allow a bounded group to wrap while retaining the same typed gap and alignment contract."
1677
+ ,"Apply the standard text or control inset to selected physical sides without an application wrapper."
1677
1678
  ],
1678
1679
  "avoidWhen": [
1679
1680
  "Do not use Row when content belongs in a semantic list or when a named-zone component such as Toolbar owns the composition.",
@@ -1703,7 +1704,8 @@
1703
1704
  "named none/2xs/xs/s/m/l/xl token gap",
1704
1705
  "typed CssLength gap",
1705
1706
  "boolean, finite-keyword, or typed CssFlex flex",
1706
- "nowrap or wrapping"
1707
+ "nowrap or wrapping",
1708
+ "top/right/bottom/left text and control inset combinations"
1707
1709
  ],
1708
1710
  "accessibility": [
1709
1711
  "Row adds layout only and no grouping role; children retain responsibility for native or ARIA semantics.",
@@ -1712,9 +1714,9 @@
1712
1714
  "geometry": {
1713
1715
  "margin": "none",
1714
1716
  "border": "none",
1715
- "padding": "none",
1717
+ "padding": "conditional",
1716
1718
  "notes": [
1717
- "Row adds no margin, border, or padding; it owns only flex participation, alignment, wrapping, and the gap between children."
1719
+ "Row adds no margin or border. textInsets adds the full 17px content inset and controlInsets adds 8px on selected physical sides; text wins when both select a side."
1718
1720
  ]
1719
1721
  },
1720
1722
  "delivery": {
@@ -1731,19 +1733,111 @@
1731
1733
  "recipe": "docs/layout.md#row-and-list-alignment"
1732
1734
  }
1733
1735
  },
1736
+ {
1737
+ "id": "spacer",
1738
+ "name": "Spacer",
1739
+ "category": "Structure",
1740
+ "kind": "component",
1741
+ "source": "kerf",
1742
+ "description": "A decorative, non-shrinking fixed gap or flexible main-axis spacer with typed physical width and height.",
1743
+ "uses": [],
1744
+ "purpose": "Express an intentional empty dimension or consume remaining flex space without application-owned spacer markup or raw CSS lengths.",
1745
+ "publicExports": ["Spacer"],
1746
+ "cssValueProps": [
1747
+ {
1748
+ "path": "width",
1749
+ "grammar": "length",
1750
+ "helpers": ["px", "rem", "em", "pct", "space", "lengthVar", "calc"],
1751
+ "nonStandaloneHelpers": ["plus"],
1752
+ "shorthands": ["none", "2xs", "xs", "s", "m", "l", "xl"],
1753
+ "canonicalShorthands": ["none", "2xs", "xs", "m", "l"],
1754
+ "exceptionalShorthands": ["s", "xl"],
1755
+ "rawPolicy": "forbid",
1756
+ "examples": ["width=\"m\"", "width={space(\"m\")}", "width={calc(plus(rem(0.25), pct(10)))}"]
1757
+ },
1758
+ {
1759
+ "path": "height",
1760
+ "grammar": "length",
1761
+ "helpers": ["px", "rem", "em", "pct", "space", "lengthVar", "calc"],
1762
+ "nonStandaloneHelpers": ["plus"],
1763
+ "shorthands": ["none", "2xs", "xs", "s", "m", "l", "xl"],
1764
+ "canonicalShorthands": ["none", "2xs", "xs", "m", "l"],
1765
+ "exceptionalShorthands": ["s", "xl"],
1766
+ "rawPolicy": "forbid",
1767
+ "examples": ["height=\"xs\"", "height={rem(1)}"]
1768
+ }
1769
+ ],
1770
+ "useWhen": [
1771
+ "Reserve a fixed empty width or height from the UI spacing scale inside a layout.",
1772
+ "Push siblings to opposite ends of a Row or other flex container by passing flex.",
1773
+ "Use a typed CssLength when the finite spacing scale cannot express the required dimension."
1774
+ ],
1775
+ "avoidWhen": [
1776
+ "Do not use Spacer for ordinary rhythm between siblings; prefer Row.gap or List.gap so the parent owns repeated spacing.",
1777
+ "Do not use Spacer to compensate for component-owned padding, margin, alignment, or responsive layout defects.",
1778
+ "Do not pass raw CSS strings to width or height."
1779
+ ],
1780
+ "alternatives": [
1781
+ {
1782
+ "id": "row",
1783
+ "relationship": "parent-spacing-alternative",
1784
+ "when": "Use Row.gap when every horizontal sibling needs the same repeated separation."
1785
+ },
1786
+ {
1787
+ "id": "list",
1788
+ "relationship": "parent-spacing-alternative",
1789
+ "when": "Use List.gap when every vertical sibling needs the same repeated separation."
1790
+ }
1791
+ ],
1792
+ "appOwns": [
1793
+ "whether the empty space carries structural meaning",
1794
+ "the named or typed dimensions",
1795
+ "whether the spacer fills the flex main axis"
1796
+ ],
1797
+ "variants": [
1798
+ "fixed named width and/or height",
1799
+ "fixed typed CssLength width and/or height",
1800
+ "flexible main-axis space"
1801
+ ],
1802
+ "accessibility": [
1803
+ "Spacer is always decorative and hidden from the accessibility tree.",
1804
+ "Spacer must not contain content or become an interaction target."
1805
+ ],
1806
+ "geometry": {
1807
+ "margin": "none",
1808
+ "border": "none",
1809
+ "padding": "none",
1810
+ "notes": [
1811
+ "A fixed Spacer does not shrink. flex changes its flex shorthand from 0 0 auto to 1 1 auto; width and height remain physical dimensions."
1812
+ ]
1813
+ },
1814
+ "delivery": {
1815
+ "browserImport": "@kerfjs/ui/spacer",
1816
+ "manualCssImport": "@kerfjs/ui/spacer.css",
1817
+ "sideEffects": ["browser-condition-css"]
1818
+ },
1819
+ "wiring": [],
1820
+ "publicClasses": ["kui-spacer"],
1821
+ "publicTokens": [],
1822
+ "links": {
1823
+ "catalogRoute": "?component=spacer",
1824
+ "documentation": "docs/layout.md",
1825
+ "recipe": "docs/layout.md#spacer"
1826
+ }
1827
+ },
1734
1828
  {
1735
1829
  "id": "list",
1736
1830
  "name": "List",
1737
1831
  "category": "Navigation",
1738
1832
  "kind": "component",
1739
1833
  "source": "kerf",
1740
- "description": "A vertical stack with physical-axis alignment, optional typed token/length gap, flex growth, vertical scrolling, and independently selectable divider edges.",
1834
+ "description": "A vertical stack with physical-axis alignment, side-selectable text/control insets, optional typed token/length gap, flex growth, vertical scrolling, and independently selectable divider edges.",
1741
1835
  "uses": [
1742
1836
  "list-header",
1743
1837
  "list-action-row",
1744
1838
  "list-item"
1745
1839
  ],
1746
- "purpose": "Standardize vertical component layout and its scroll/divider ownership instead of repeating ad-hoc flex-column wrappers.",
1840
+ "purpose": "Standardize vertical component layout, optional physical-side text/control insets, and scroll/divider ownership instead of repeating ad-hoc flex-column wrappers.",
1747
1841
  "publicExports": [
1748
1842
  "List",
1749
1843
  "px",
@@ -1783,7 +1877,8 @@
1783
1877
  "Arrange component rows or sections in a vertical stretch-aligned stack.",
1784
1878
  "Let one bounded list own vertical scrolling, flex growth, or one or more physical-edge dividers.",
1785
1879
  "Choose a finite gap shorthand such as xs or m, or compose a typed length with @kerfjs/ui/css-values when the spacing scale is insufficient.",
1786
- "Use a finite flex keyword or the flex() builder when List participates in flexible layout."
1880
+ "Use a finite flex keyword or the flex() builder when List participates in flexible layout.",
1881
+ "Apply the standard text or control inset to selected physical sides without an application wrapper."
1787
1882
  ,"Align children horizontally or distribute them vertically with the same physical alignment vocabulary as Row."
1788
1883
  ],
1789
1884
  "avoidWhen": [
@@ -1806,7 +1901,8 @@
1806
1901
  "scrollable",
1807
1902
  "left/center/right/full horizontal alignment",
1808
1903
  "top/middle/bottom/full vertical alignment",
1809
- "top/right/bottom/left divider combinations"
1904
+ "top/right/bottom/left divider combinations",
1905
+ "top/right/bottom/left text and control inset combinations"
1810
1906
  ],
1811
1907
  "accessibility": [
1812
1908
  "List adds layout only and no list role; children retain responsibility for their native or ARIA semantics.",
@@ -1815,9 +1911,9 @@
1815
1911
  "geometry": {
1816
1912
  "margin": "none",
1817
1913
  "border": "conditional",
1818
- "padding": "none",
1914
+ "padding": "conditional",
1819
1915
  "notes": [
1820
- "List adds no margin or padding; dividerSides draws only the selected physical edges without changing box geometry."
1916
+ "List adds no margin. dividerSides draws selected physical edges without changing geometry; textInsets adds the full 17px content inset and controlInsets adds 8px on selected sides, with text winning on overlap."
1821
1917
  ]
1822
1918
  },
1823
1919
  "delivery": {
@@ -1849,12 +1945,12 @@
1849
1945
  "category": "Navigation",
1850
1946
  "kind": "component",
1851
1947
  "source": "kerf",
1852
- "description": "Configurable section headings with semantic count/status indicators, density, group dividers, a logical-end action, disclosure states, and protected native-popover trigger metadata.",
1948
+ "description": "Configurable section headings with full-width or shrink-wrapped inline presentation, semantic count/status indicators, density, group dividers, a logical-end action, disclosure states, and protected native-popover trigger metadata.",
1853
1949
  "uses": [
1854
1950
  "lucide-icon",
1855
1951
  "disclosure-arrow"
1856
1952
  ],
1857
- "purpose": "Fill the available section width while splitting a dormant title and mutually exclusive semantic count or legacy badge from an optional logical-end 44px action with an 18px visual, or render that title cluster as a disclosure button with a default production DisclosureArrow.",
1953
+ "purpose": "Fill the available section width by default, or shrink-wrap without outer geometry when inline, while splitting a dormant title and mutually exclusive semantic count or legacy badge from an optional logical-end action with an 18px visual; disclosure mode renders the title cluster as a button with a default production DisclosureArrow.",
1858
1954
  "publicExports": [
1859
1955
  "ListHeader"
1860
1956
  ],
@@ -1887,6 +1983,7 @@
1887
1983
  "action",
1888
1984
  "toggle",
1889
1985
  "placeholder (loading)",
1986
+ "inline shrink-wrapped presentation",
1890
1987
  "compact density",
1891
1988
  "neutral, accent, pop, or danger indicator tone",
1892
1989
  "before, after, or paired group divider"
@@ -2186,9 +2283,9 @@
2186
2283
  "category": "Structure",
2187
2284
  "kind": "component",
2188
2285
  "source": "kerf",
2189
- "description": "Insets a self-bordered control into a pane/list content region.",
2286
+ "description": "Insets selected physical sides of a self-bordered control in a pane/list content region.",
2190
2287
  "uses": [],
2191
- "purpose": "Insets a self-bordered control into a pane/list content region.",
2288
+ "purpose": "Apply the standard 8px outer margin to all physical sides of a self-bordered control by default, or only the sides selected with the canonical Sides contract.",
2192
2289
  "publicExports": [
2193
2290
  "ListInsetControl"
2194
2291
  ],
@@ -2207,6 +2304,10 @@
2207
2304
  "appOwns": [
2208
2305
  "the wrapped control and its behavior"
2209
2306
  ],
2307
+ "variants": [
2308
+ "all-side inset by default",
2309
+ "top/right/bottom/left inset combinations"
2310
+ ],
2210
2311
  "accessibility": [
2211
2312
  "A layout wrapper only; it adds no role or name — the wrapped control keeps its own."
2212
2313
  ],
@@ -2241,9 +2342,9 @@
2241
2342
  "category": "Structure",
2242
2343
  "kind": "component",
2243
2344
  "source": "kerf",
2244
- "description": "Gives bare text the content-item geometry so it lines up with bordered items.",
2345
+ "description": "Gives selected physical sides of bare text the content-item geometry so it lines up with bordered items.",
2245
2346
  "uses": [],
2246
- "purpose": "Gives bare text the content-item geometry so it lines up with bordered items.",
2347
+ "purpose": "Apply the full 8px margin + 1px transparent border + 8px padding to all physical sides of bare text by default, or only the sides selected with the canonical Sides contract.",
2247
2348
  "publicExports": [
2248
2349
  "ListInsetText"
2249
2350
  ],
@@ -2262,6 +2363,11 @@
2262
2363
  "appOwns": [
2263
2364
  "the text content"
2264
2365
  ],
2366
+ "variants": [
2367
+ "all-side inset by default",
2368
+ "top/right/bottom/left inset combinations",
2369
+ "deprecated horizontalOnly compatibility alias for sides=\"rl\""
2370
+ ],
2265
2371
  "accessibility": [
2266
2372
  "A layout wrapper only; it adds no role or name."
2267
2373
  ],
@@ -1,6 +1,6 @@
1
1
  # Public API signatures for the UI authoring corpus
2
2
 
3
- Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.35` and `kerfjs@5.0.0-beta.35`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
3
+ Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.37` and `kerfjs@5.0.0-beta.37`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
4
4
 
5
5
  ## `@kerfjs/ui/css-values`
6
6
 
@@ -105,7 +105,7 @@ export { DisclosureArrow, type DisclosureArrowProps, type DisclosureDirection };
105
105
 
106
106
  ```ts
107
107
  import * as kerfjs from 'kerfjs';
108
- import { D as DividerSides } from './divider-sides-267FA7sY.js';
108
+ import { D as DividerSides } from './divider-sides-BzB6rphT.js';
109
109
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
110
110
 
111
111
  interface ToolbarProps {
@@ -257,6 +257,7 @@ type ListHeaderRootAttributes = Readonly<Record<`data-${string}`, string | undef
257
257
  'data-has-count'?: never;
258
258
  'data-density'?: never;
259
259
  'data-divider'?: never;
260
+ 'data-inline'?: never;
260
261
  'data-indicator-tone'?: never;
261
262
  'data-toggle'?: never;
262
263
  }>;
@@ -271,6 +272,8 @@ interface ListHeaderBaseProps {
271
272
  label: string;
272
273
  density?: 'standard' | 'compact';
273
274
  divider?: 'none' | 'before' | 'after' | 'both';
275
+ /** Shrink-wrap the header without its default outer margin, border, or padding. */
276
+ inline?: boolean;
274
277
  indicatorTone?: 'neutral' | 'accent' | 'pop' | 'danger';
275
278
  /** Render as an unanimated loading skeleton: keep the label and action affordance, disable interaction. */
276
279
  placeholder?: boolean;
@@ -322,7 +325,7 @@ type ListHeaderIndicatorProps = {
322
325
  status?: SafeHtml;
323
326
  };
324
327
  type ListHeaderProps = ListHeaderBaseProps & ListHeaderIndicatorProps & ListHeaderModeProps;
325
- declare function ListHeader({ label, count, countLabel, badge, status, density, divider, indicatorTone, action, actionLabel, actionIcon, actionDisabled, disabledReason, expanded, toggle, placeholder, rootAttributes, triggerAttributes, }: ListHeaderProps): SafeHtml;
328
+ declare function ListHeader({ label, count, countLabel, badge, status, density, divider, inline, indicatorTone, action, actionLabel, actionIcon, actionDisabled, disabledReason, expanded, toggle, placeholder, rootAttributes, triggerAttributes, }: ListHeaderProps): SafeHtml;
326
329
 
327
330
  export { ListHeader, type ListHeaderProps };
328
331
  ```
@@ -332,7 +335,7 @@ export { ListHeader, type ListHeaderProps };
332
335
  ```ts
333
336
  import * as kerfjs from 'kerfjs';
334
337
  import { UiSpaceName, CssLength, CssFlexKeyword, CssFlex } from './css-values.js';
335
- import { D as DividerSides } from './divider-sides-267FA7sY.js';
338
+ import { D as DividerSides, S as Sides } from './divider-sides-BzB6rphT.js';
336
339
  import { H as HorizontalAlignment, V as VerticalAlignment } from './flex-alignment-CF8NLj7i.js';
337
340
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
338
341
 
@@ -350,12 +353,16 @@ interface ListProps {
350
353
  scrollable?: boolean;
351
354
  /** Physical divider edges in canonical top/right/bottom/left order. */
352
355
  dividerSides?: DividerSides;
356
+ /** Physical sides that receive the standard 17px text inset. */
357
+ textInsets?: Sides;
358
+ /** Physical sides that receive the standard 8px control inset. Text insets win on overlap. */
359
+ controlInsets?: Sides;
353
360
  className?: string;
354
361
  }
355
362
  /** A stretch-aligned vertical stack with optional gap, flex, scroll, and dividers. */
356
- declare function List({ children, gap, flex, hAlign, vAlign, scrollable, dividerSides, className, }: ListProps): kerfjs.SafeHtml;
363
+ declare function List({ children, gap, flex, hAlign, vAlign, scrollable, dividerSides, textInsets, controlInsets, className, }: ListProps): kerfjs.SafeHtml;
357
364
 
358
- export { CssFlex, CssFlexKeyword, CssLength, DividerSides, HorizontalAlignment, List, type ListProps, UiSpaceName, VerticalAlignment };
365
+ export { CssFlex, CssFlexKeyword, CssLength, DividerSides, HorizontalAlignment, List, type ListProps, Sides, UiSpaceName, VerticalAlignment };
359
366
  ```
360
367
 
361
368
  ## `@kerfjs/ui/list-action-row`
@@ -478,11 +485,14 @@ export { ListItem, type ListItemProps };
478
485
 
479
486
  ```ts
480
487
  import * as kerfjs from 'kerfjs';
488
+ import { S as Sides } from './divider-sides-BzB6rphT.js';
481
489
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
482
490
 
483
491
  interface ListInsetControlProps {
484
492
  /** Control(s) that own their own border and padding (e.g. an input, a `wa-*`). */
485
493
  children: KerfUiContent;
494
+ /** Physical inset sides in canonical top/right/bottom/left order. Defaults to all sides. */
495
+ sides?: Sides;
486
496
  className?: string;
487
497
  }
488
498
  /**
@@ -491,22 +501,25 @@ interface ListInsetControlProps {
491
501
  * Use it for controls that carry their own border and padding but no outer margin
492
502
  * — the wrapper adds only the alignment margin and layout, not a second inset.
493
503
  */
494
- declare function ListInsetControl({ children, className, }: ListInsetControlProps): kerfjs.SafeHtml;
504
+ declare function ListInsetControl({ children, sides, className, }: ListInsetControlProps): kerfjs.SafeHtml;
495
505
 
496
- export { ListInsetControl, type ListInsetControlProps };
506
+ export { ListInsetControl, type ListInsetControlProps, Sides };
497
507
  ```
498
508
 
499
509
  ## `@kerfjs/ui/list-inset-text`
500
510
 
501
511
  ```ts
502
512
  import * as kerfjs from 'kerfjs';
513
+ import { S as Sides } from './divider-sides-BzB6rphT.js';
503
514
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
504
515
 
505
516
  interface ListInsetTextProps {
506
517
  /** Text (or inline content) that carries no margin, border, or padding of its own. */
507
518
  children: KerfUiContent | string;
519
+ /** Physical inset sides in canonical top/right/bottom/left order. Defaults to all sides. */
520
+ sides?: Sides;
508
521
  /**
509
- * Keep only the horizontal geometry (inline margin, left/right border, and
522
+ * @deprecated Pass `sides="rl"` instead. Keep only the horizontal geometry (inline margin, left/right border, and
510
523
  * left/right padding) and drop the vertical margin, border, and padding. Use it
511
524
  * when the text edge must still align with bordered items but the line should not
512
525
  * add its own vertical box space — tight text layout inside a content region.
@@ -522,9 +535,9 @@ interface ListInsetTextProps {
522
535
  * Pass `horizontalOnly` to keep the horizontal inset but drop the vertical box
523
536
  * space for tight text layout.
524
537
  */
525
- declare function ListInsetText({ children, horizontalOnly, className, }: ListInsetTextProps): kerfjs.SafeHtml;
538
+ declare function ListInsetText({ children, sides, horizontalOnly, className, }: ListInsetTextProps): kerfjs.SafeHtml;
526
539
 
527
- export { ListInsetText, type ListInsetTextProps };
540
+ export { ListInsetText, type ListInsetTextProps, Sides };
528
541
  ```
529
542
 
530
543
  ## `@kerfjs/ui/value-table`
@@ -1996,6 +2009,7 @@ export { DialogSurface, type DialogSurfacePresentation, type DialogSurfaceProps,
1996
2009
  ```ts
1997
2010
  import * as kerfjs from 'kerfjs';
1998
2011
  import { UiSpaceName, CssLength, CssFlexKeyword, CssFlex } from './css-values.js';
2012
+ import { S as Sides } from './divider-sides-BzB6rphT.js';
1999
2013
  import { H as HorizontalAlignment, V as VerticalAlignment } from './flex-alignment-CF8NLj7i.js';
2000
2014
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
2001
2015
 
@@ -2011,10 +2025,35 @@ interface RowProps {
2011
2025
  flex?: boolean | CssFlexKeyword | CssFlex;
2012
2026
  /** Allow children to wrap onto additional lines. */
2013
2027
  wrap?: boolean;
2028
+ /** Physical sides that receive the standard 17px text inset. */
2029
+ textInsets?: Sides;
2030
+ /** Physical sides that receive the standard 8px control inset. Text insets win on overlap. */
2031
+ controlInsets?: Sides;
2014
2032
  className?: string;
2015
2033
  }
2016
2034
  /** A horizontal flex row with explicit physical-axis alignment and spacing. */
2017
- declare function Row({ children, hAlign, vAlign, gap, flex, wrap, className, }: RowProps): kerfjs.SafeHtml;
2035
+ declare function Row({ children, hAlign, vAlign, gap, flex, wrap, textInsets, controlInsets, className, }: RowProps): kerfjs.SafeHtml;
2018
2036
 
2019
- export { CssFlex, CssFlexKeyword, CssLength, HorizontalAlignment, Row, type RowProps, UiSpaceName, VerticalAlignment };
2037
+ export { CssFlex, CssFlexKeyword, CssLength, HorizontalAlignment, Row, type RowProps, Sides, UiSpaceName, VerticalAlignment };
2038
+ ```
2039
+
2040
+ ## `@kerfjs/ui/spacer`
2041
+
2042
+ ```ts
2043
+ import * as kerfjs from 'kerfjs';
2044
+ import { UiSpaceName, CssLength } from './css-values.js';
2045
+
2046
+ interface SpacerProps {
2047
+ /** Grow and shrink to fill the available space along a parent's flex axis. */
2048
+ flex?: boolean;
2049
+ /** A named UI spacing token or typed physical width. */
2050
+ width?: UiSpaceName | CssLength;
2051
+ /** A named UI spacing token or typed physical height. */
2052
+ height?: UiSpaceName | CssLength;
2053
+ className?: string;
2054
+ }
2055
+ /** A decorative fixed-size or flexible gap for Row, List, and other flex layouts. */
2056
+ declare function Spacer({ flex, width, height, className, }: SpacerProps): kerfjs.SafeHtml;
2057
+
2058
+ export { CssLength, Spacer, type SpacerProps, UiSpaceName };
2020
2059
  ```
package/ai/skill.md CHANGED
@@ -128,13 +128,13 @@ Hard rules:
128
128
  12. Demo work uses public production component subpaths and their browser-selected CSS. Give every public visual component its own category-grouped catalog route; list themed third-party components under a clearly labeled collapsible ecosystem section, with a focused route for each. For the complete focused/composition decision, `CatalogExample` / `CatalogExampleStack` row/group nesting, exact specimen selection, geometry-overlay legend and exclusions, skip-marker behavior, and metadata ownership, follow the single [Catalog demo authoring contract](../docs/catalog.md#catalog-demo-authoring-contract), discovered machine-readably through [`catalog-authoring.json`](./catalog-authoring.json). Never infer structure from private `kui-catalog-*` classes. Project each component's deterministic repository-relative demo source and existing documentation path so the detail can expose `View demo source` and `Read guidance` links without a runtime export; also derive first-party component implementation paths from their canonical browser imports, and label Web Awesome documentation as Kerf integration guidance. Declare direct `uses` relationships so `Used by` stays derivable, and theme shell chrome through the same semantic tokens as the stage instead of drawing a substitute. Enable `wireCatalog({ revealSelection: true })` for long desktop sidebars so controlled selections remain visible without moving focus; keep its default compact media guard unless compact scrolling is an explicit product behavior, and use `revealCatalogEntry` for an initial deep link.
129
129
  13. The Web Awesome theme makes Tooltip and Popover arrowless by default. Keep that default unless a pointer materially clarifies the anchor; opt back in with `--wa-tooltip-arrow-size`, `--kui-wa-popover-arrow-size`, or a popover's public `--arrow-size`, and use `without-arrow` when local no-arrow intent should survive theme changes. Its non-field chrome uses `--kui-wa-control-inset` (8px controls/items), `--kui-wa-surface-margin` / `--kui-wa-surface-inset` (8px around/inside Accordion, Card, Details, Callout, and Include), and `--kui-wa-container-inset` (16px Tab Panel content); Dialog maps its body to the 8px surface tier and its footer to the 16px container tier independently of `--spacing`. Override those tiers instead of restyling individual parts. When a dialog provides another dismissal affordance, put `hide-actions` on the `wa-dialog` host to hide its directly exported `header-actions` part; never chain `::part()` selectors through internal parts. Checkbox and Radio Group option regions, the Color Picker trigger, and Slider's complete interactive region receive the shared 8px logical inline outer inset because those controls have no bordered field shell. Known Date field captions and bordered text-like field hints align with values at the 9px border-plus-padding inset. OTP Input's label uses the same uppercase xs/650 treatment as other field labels, and both its label and hint use that 9px inset.
130
130
  14. Treat the complete Web Awesome catalog as support coverage, not a recommendation list. Its UX sidebar marks superseded and exceptional entries `Discouraged`. Consider Popup when it replaces custom anchored positioning. Prefer Kerf `Select` over direct Dropdown/Dropdown Item/Select/Option composition, `SegmentedControl` over Button Group, `TabBar` or `SegmentedControl` over Web Awesome Tabs, `LucideIcon` over Web Awesome Icon, and `ResizableRegion` over Split Panel. Use Tree/Tree Item, Animated Image, and Comparison only for a specific required behavior; avoid Zoomable Frame.
131
- 15. Build sidebars, main areas, inspectors, and dialogs from `@kerfjs/ui/layout.css`: an unpadded `.kui-pane`, optional `.kui-pane__toolbar`, one scrolling `.kui-pane__content`, and optional `.kui-pane__footer`. Use `Row` instead of a hand-written flex-row wrapper and `List` instead of a hand-written flex-column wrapper when children need shared physical-axis alignment and typed spacing. `Row` defaults to left/full with an `xs` gap and no wrapping; `List` retains full/top and zero-gap defaults. Use `List` when rows or sections also need flex growth, one vertical scroll owner, or physical-edge dividers; `dividerSides` uses canonical `t`/`r`/`b`/`l` order. A dialog body is generally a `List`: set `DialogSurface bodyInset="none"` when its list children own geometry, and wrap bare dialog prose in `ListInsetText` so it aligns with bordered siblings without a second inset. Add `.kui-content` for 24px major vertical separation and `.kui-content-item` for a child-owned 8px inline margin, 1px transparent-or-visible border, 8px padding, and 12px radius. Use the pill modifier for 22px. Do not pad pane shells, duplicate item geometry in wrappers, or create nested scroll owners.
131
+ 15. Build sidebars, main areas, inspectors, and dialogs from `@kerfjs/ui/layout.css`: an unpadded `.kui-pane`, optional `.kui-pane__toolbar`, one scrolling `.kui-pane__content`, and optional `.kui-pane__footer`. Use `Row` instead of a hand-written flex-row wrapper and `List` instead of a hand-written flex-column wrapper when children need shared physical-axis alignment and typed spacing. `Row` defaults to left/full with an `xs` gap and no wrapping; `List` retains full/top and zero-gap defaults. Use `List` when rows or sections also need flex growth, one vertical scroll owner, or physical-edge dividers. `dividerSides`, `textInsets`, and `controlInsets` use canonical physical `t`/`r`/`b`/`l` order; text geometry wins where text and control selections overlap. `ListInsetText` and `ListInsetControl` inset all four sides by default and accept the same `sides` grammar for selective edges. A dialog body is generally a `List`: set `DialogSurface bodyInset="none"` when its list children own geometry, and wrap bare dialog prose in `ListInsetText` so it aligns with bordered siblings without a second inset. Add `.kui-content` for 24px major vertical separation and `.kui-content-item` for a child-owned 8px inline margin, 1px transparent-or-visible border, 8px padding, and 12px radius. Use the pill modifier for 22px. Do not pad pane shells, duplicate item geometry in wrappers, or create nested scroll owners.
132
132
  16. Keep a visible collapsible pane's collapse control in its own toolbar. When hidden, move its restore control into the adjacent main toolbar on the corresponding logical edge: leading for an inline-start sidebar and trailing for an inline-end inspector. Collapse the pane completely; do not preserve an empty icon-only rail.
133
- 17. The only direct children of a `Toolbar` zone (`leading`/`center`/`trailing`) are `ToolbarText` (identity/title text) and `ToolbarControlGroup`; never drop bare buttons, inputs, links, or arbitrary markup straight into a zone. Put identity/title text directly in the zone as `ToolbarText`; wrap interactive controls and icon tiles in `ToolbarControlGroup`. `SegmentedControl`, `Select`, a collapsible `TokenSearchField`, and Web Awesome controls all live inside a group. For an ordinary icon/action control inside a group, use a plain `<button>` (the group styles `> button` fully) — that is the default; reach for `wa-button` only when you need a Web Awesome feature, chiefly a `wa-dropdown` popup trigger. A popup menu in a toolbar is a `single` group wrapping a `wa-dropdown` whose `slot="trigger"` `wa-button` is the toolbar button and whose `wa-dropdown-item`s are the menu, with the dropdown kept under `data-morph-skip-children`. For a compact mixed group, keep the standard item padding and raised selected chrome; the selected item paints over the outer border without an internal separator. For an avatar control, set `content="avatar"` and `avatarImage` on the group instead of inserting an `img`: a lone control paints the contained image on the outer group and a multi-button group paints it only on the pressed selection highlight. Compose panel/dialog/page headings with a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Split dormant and interactive regions: `ListHeader` fills the available inline width and keeps its label and mutually exclusive semantic count or non-count `badge` together, with an independent logical-end 44px action unless disclosure mode makes the title cluster the button. Its action visual defaults to 18px through `--kui-list-header-action-icon-size`; never shrink the target to match it. Pass every non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a number in `badge`. `ListItem.trailing` is dormant; use `ListActionRow` when primary and trailing actions need sibling 44px native buttons. Its `label`, `icon`, and `trailingActionIcon` slots are also dormant and cannot contain controls. Let panes relocate at narrow widths instead of shrinking targets.
133
+ 17. The only direct children of a `Toolbar` zone (`leading`/`center`/`trailing`) are `ToolbarText` (identity/title text) and `ToolbarControlGroup`; never drop bare buttons, inputs, links, or arbitrary markup straight into a zone. Put identity/title text directly in the zone as `ToolbarText`; wrap interactive controls and icon tiles in `ToolbarControlGroup`. `SegmentedControl`, `Select`, a collapsible `TokenSearchField`, and Web Awesome controls all live inside a group. For an ordinary icon/action control inside a group, use a plain `<button>` (the group styles `> button` fully) — that is the default; reach for `wa-button` only when you need a Web Awesome feature, chiefly a `wa-dropdown` popup trigger. A popup menu in a toolbar is a `single` group wrapping a `wa-dropdown` whose `slot="trigger"` `wa-button` is the toolbar button and whose `wa-dropdown-item`s are the menu, with the dropdown kept under `data-morph-skip-children`. For a compact mixed group, keep the standard item padding and raised selected chrome; the selected item paints over the outer border without an internal separator. For an avatar control, set `content="avatar"` and `avatarImage` on the group instead of inserting an `img`: a lone control paints the contained image on the outer group and a multi-button group paints it only on the pressed selection highlight. Compose panel/dialog/page headings with a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Split dormant and interactive regions: `ListHeader` fills the available inline width by default; set `inline` only when it should shrink-wrap in surrounding content without outer margin, border, or padding. It keeps its label and mutually exclusive semantic count or non-count `badge` together, with an independent logical-end action unless disclosure mode makes the title cluster the button. Its action visual defaults to 18px through `--kui-list-header-action-icon-size`; never shrink the target to match it. Pass every non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a number in `badge`. `ListItem.trailing` is dormant; use `ListActionRow` when primary and trailing actions need sibling native buttons. Its `label`, `icon`, and `trailingActionIcon` slots are also dormant and cannot contain controls. Let panes relocate at narrow widths instead of shrinking targets.
134
134
  18. When a recurring concept has no matching export or production recipe, keep its semantics in a thin application adapter while reusing the public layout vocabulary. The composer recipe uses one visible form surface, toolbar title/supporting-copy ids, shared 8px field/action gutters, 24px major rhythm, and a conditional StateBanner as its only nested semantic surface; do not turn every section into a card or double-inset intrinsically bordered controls. The application-local `../docs/examples/command-palette-adapter.tsx` is reference source for one such missing concept, not an `@kerfjs/ui` runtime export or catalog recipe. The application owns its registration, ranking, history, permissions, availability, shortcut policy, focus policy, dispatch, and copy. If a missing concept recurs across products, open an upstream component or recipe request.
135
135
  19. Compose `ValueTable` from typed `ValueTableRow` entries instead of handwritten `dt`/`dd` wrappers. Pass `icon` for the optional 24px leading visual; the row owns 8px of root-scaled top and bottom padding, the 8px iconless or 40px icon-bearing separator start, and the common 8px right inset.
136
136
  20. Use the standard direct `Toolbar` composition for a panel, dialog, or page heading, not a private wrapper or custom heading row. Put an optional icon in a `ToolbarControlGroup`, the title directly in the leading zone as extra-large `ToolbarText`, and actions in a trailing group. Set `headingLevel` for page/section landmarks. Supporting copy stays below the toolbar as app-owned content. The app owns modal behavior, focus, dismissal, command policy, ids, and action handling.
137
- 21. Space with the official five-step scale, picked by how connected two elements are — not by eye. `0` `--kui-space-none` = no separation (one unit); `4px` `--kui-space-2xs` = very minor air on a connected cluster; `8px` `--kui-space-xs` = standard, between elements within a group; `16px` `--kui-space-m` = minor, between homogeneous groups; `24px` `--kui-space-l` = major, between heterogeneous groups (the `.kui-content` rhythm). The 8px-vs-24px distinction is inside-a-group vs between-major-differing-regions. `--kui-space-s` (12px) and `--kui-space-xl` (32px) are off-scale exceptions. For `Row.gap` or `List.gap`, pass a finite shorthand such as `gap="xs"` or `gap="m"`; when one token is insufficient, import typed builders from the CSS-free `@kerfjs/ui/css-values` subpath. Preserve property grammar: lengths for gap/Skeleton geometry, `flex()` for List flex, and `uiColor()`/`colorVar()` for choice icons. Never cast between brands or emit raw row `style` declarations. `plus` is not standalone and must be wrapped in `calc`; `remify()` remains source-CSS syntax and is never a runtime prop value. See `docs/layout.md` "Spacing scale".
137
+ 21. Space with the official five-step scale, picked by how connected two elements are — not by eye. `0` `--kui-space-none` = no separation (one unit); `4px` `--kui-space-2xs` = very minor air on a connected cluster; `8px` `--kui-space-xs` = standard, between elements within a group; `16px` `--kui-space-m` = minor, between homogeneous groups; `24px` `--kui-space-l` = major, between heterogeneous groups (the `.kui-content` rhythm). The 8px-vs-24px distinction is inside-a-group vs between-major-differing-regions. `--kui-space-s` (12px) and `--kui-space-xl` (32px) are off-scale exceptions. For `Row.gap` or `List.gap`, pass a finite shorthand such as `gap="xs"` or `gap="m"`; use `Spacer` for one intentional typed empty width/height or a flexible main-axis gap. When one token is insufficient, import typed builders from the CSS-free `@kerfjs/ui/css-values` subpath. Preserve property grammar: lengths for gap/Spacer/Skeleton geometry, `flex()` for List flex, and `uiColor()`/`colorVar()` for choice icons. Never cast between brands or emit raw row `style` declarations. `plus` is not standalone and must be wrapped in `calc`; `remify()` remains source-CSS syntax and is never a runtime prop value. See `docs/layout.md` "Spacing scale".
138
138
  22. Pick a whole-screen layout from the opt-in, tree-shakeable subpaths by data + interaction + device, and derive responsiveness from `@kerfjs/ui/device-class`'s `deviceClass()` (`compact` = handset or portrait tablet = one pane at a time). Simple/flat → `NavStack` with one entry (single pane), plus `TabScaffold` (`@kerfjs/ui/tab-scaffold`, iOS bottom tabs, each tab its own `NavStack`) for 2–5 co-equal sections on `compact`. Drill-down → `NavStack` (`@kerfjs/ui/nav-stack`), upgrading to `SplitView` (`@kerfjs/ui/split-view`, list-detail) once both panes fit (`atLeast('tablet')` landscape); `SplitView` collapses to a `NavStack` on `compact`. Keep each stack's ordered views controlled; put view-specific bottom chrome on `NavStackView.bottomToolbar` (or use the component-level persistent fallback), then call `wireNavStack` so content slides while the active view's top and bottom chrome cross-fade and focus moves into the new top view/restores on pop. Mark a preferred initial heading or control with `data-nav-focus` when DOM order is not sufficient. Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each ships a companion CSS import and stays out of the barrel. For a standalone collapsible rail or bottom drawer outside a full shell, use `CollapsiblePanel` + `CollapsiblePanelToggle` (`@kerfjs/ui/collapsible-panel`) with `wireSidebar` (`@kerfjs/ui/wire-sidebar`): the standard collapse animation and per-side icon convention (`PanelLeft*`/`PanelRight*`/`PanelBottom*`) plus toggle, focus, compact overlay, and persistence semantics. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). See `docs/app-layouts.md` and `docs/collapsible-panel.md`.
139
139
 
140
140
  Common mistakes: