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

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/README.md CHANGED
@@ -4,6 +4,15 @@ Accessible, composable UI primitives for [kerf](https://github.com/brianwestphal
4
4
 
5
5
  Start interface work with the [need-first component selection matrix](./docs/component-selection.md). It distinguishes direct reuse, composition, thin application adapters, and genuinely custom semantics, with imports, required wiring, application ownership, alternatives, and recipes for every public primitive.
6
6
 
7
+ When adding a first-party public component, follow the
8
+ [component integration workflow](./docs/component-integration.md). The catalog
9
+ entry drives one validator across package, build, barrel, CSS, demo, route, and
10
+ AI signature projections, with a non-mutating dry-run report.
11
+
12
+ Browser verification always builds current source: both `npm run test:e2e`
13
+ and direct focused Playwright commands run the catalog/conformance preflights
14
+ and production build before preview. See the [UX catalog contract](./docs/ux-demo.md).
15
+
7
16
  When authoring a component gallery, follow the single
8
17
  [Catalog demo authoring contract](./docs/catalog.md#catalog-demo-authoring-contract)
9
18
  for focused-versus-composition modes, helper nesting, specimen selection,
@@ -117,7 +126,7 @@ Do not concatenate a count into `label` or pass a numeric `badge`; the mutually
117
126
  exclusive legacy `badge` slot remains available for non-count `SafeHtml` such
118
127
  as a `New` marker.
119
128
 
120
- `List.gap` accepts boolean default spacing, a direct spacing name such as
129
+ `Row.gap` and `List.gap` accept a direct spacing name such as
121
130
  `gap="xs"` or `gap="m"`, or a branded `CssLength`. Import `space`, `rem`, `em`,
122
131
  `px`, `pct`, `lengthVar`, `plus`, and `calc` from the CSS-free
123
132
  `@kerfjs/ui/css-values` subpath when a named step is not enough. `plus` returns
@@ -125,8 +134,14 @@ an incomplete expression, so wrap it with `calc` before passing it to a prop.
125
134
  Raw CSS strings are deliberately rejected; the opaque brands catch authoring
126
135
  mistakes but are not sanitizers.
127
136
 
137
+ Use `Row` for horizontal flex composition and `List` for vertical composition.
138
+ Both accept `hAlign` (`left`, `center`, `right`, `full`) and `vAlign` (`top`,
139
+ `middle`, `bottom`, `full`), plus their documented short/CSS aliases. `Row`
140
+ defaults to left/full alignment, the `xs` gap, and `wrap={false}`; List keeps
141
+ its existing full/top alignment and zero-gap defaults.
142
+
128
143
  The same property-specific boundary applies beyond spacing: use `flex()` (or a
129
- finite keyword) for `List.flex`; length builders and intrinsic-size keywords for
144
+ finite keyword) for `Row.flex` and `List.flex`; length builders and intrinsic-size keywords for
130
145
  `Skeleton.width`/`height`; length builders for `Skeleton.radius`; and
131
146
  `uiColor()` or `colorVar()` for `SelectChoice.color`. These grammars are not
132
147
  interchangeable. `ListItem` and `ListActionRow` deliberately expose no raw
@@ -144,6 +159,7 @@ interchangeable. `ListItem` and `ListActionRow` deliberately expose no raw
144
159
  | `FloatingToolbar` | `@kerfjs/ui/floating-toolbar` | `@kerfjs/ui/floating-toolbar.css` |
145
160
  | `ToolbarText` | `@kerfjs/ui/toolbar-text` | `@kerfjs/ui/toolbar-text.css` |
146
161
  | `List` | `@kerfjs/ui/list` | `@kerfjs/ui/list.css` |
162
+ | `Row` | `@kerfjs/ui/row` | `@kerfjs/ui/row.css` |
147
163
  | Typed CSS dimension builders | `@kerfjs/ui/css-values` | — |
148
164
  | `ListActionRow` | `@kerfjs/ui/list-action-row` | `@kerfjs/ui/list-action-row.css` |
149
165
  | `ListItem` | `@kerfjs/ui/list-item` | `@kerfjs/ui/list-item.css` |
@@ -98,6 +98,16 @@
98
98
  "requiredConcepts": ["control"]
99
99
  }
100
100
  },
101
+ "@kerfjs/ui:row": {
102
+ "_source": "docs/layout.md#row-and-list-alignment",
103
+ "contexts": ["application-ui", "horizontal-layout"],
104
+ "children": {
105
+ "mode": "any",
106
+ "concepts": [],
107
+ "requiredConcepts": []
108
+ },
109
+ "zones": []
110
+ },
101
111
  "@kerfjs/ui:list": {
102
112
  "_source": "docs/component-selection.md#problem-to-component-matrix",
103
113
  "contexts": ["application-ui", "navigation-list", "vertical-layout"],
@@ -1401,6 +1401,91 @@
1401
1401
  "composition": "generated-permissive-default"
1402
1402
  }
1403
1403
  },
1404
+ {
1405
+ "key": "@kerfjs/ui:row",
1406
+ "package": "@kerfjs/ui",
1407
+ "id": "row",
1408
+ "name": "Row",
1409
+ "kind": "component",
1410
+ "source": "kerf",
1411
+ "parents": { "mode": "any", "entries": [] },
1412
+ "contexts": ["application-ui", "horizontal-layout"],
1413
+ "zones": [],
1414
+ "children": { "mode": "any", "concepts": [], "requiredConcepts": [] },
1415
+ "state": [
1416
+ {
1417
+ "id": "child semantics and content",
1418
+ "owner": "application",
1419
+ "required": false
1420
+ },
1421
+ {
1422
+ "id": "alignment and wrapping policy",
1423
+ "owner": "application",
1424
+ "required": false
1425
+ },
1426
+ {
1427
+ "id": "typed gap and flex choices",
1428
+ "owner": "application",
1429
+ "required": false
1430
+ }
1431
+ ],
1432
+ "wiring": { "required": false, "helpers": [], "obligations": [] },
1433
+ "responsive": { "owner": "not-applicable", "behaviors": [] },
1434
+ "layout": {
1435
+ "roles": ["structure"],
1436
+ "geometry": {
1437
+ "margin": "none",
1438
+ "border": "none",
1439
+ "padding": "none",
1440
+ "notes": [
1441
+ "Row adds no margin, border, or padding; it owns only flex participation, alignment, wrapping, and the gap between children."
1442
+ ]
1443
+ }
1444
+ },
1445
+ "accessibility": {
1446
+ "obligations": [
1447
+ "Row adds layout only and no grouping role; children retain responsibility for native or ARIA semantics.",
1448
+ "Wrapping must preserve a readable source and focus order."
1449
+ ]
1450
+ },
1451
+ "cssValueProps": [
1452
+ {
1453
+ "path": "gap",
1454
+ "grammar": "length",
1455
+ "helpers": ["px", "rem", "em", "pct", "space", "lengthVar", "calc"],
1456
+ "nonStandaloneHelpers": ["plus"],
1457
+ "shorthands": ["none", "2xs", "xs", "s", "m", "l", "xl"],
1458
+ "canonicalShorthands": ["none", "2xs", "xs", "m", "l"],
1459
+ "exceptionalShorthands": ["s", "xl"],
1460
+ "rawPolicy": "forbid",
1461
+ "examples": [
1462
+ "gap=\"xs\"",
1463
+ "gap={space(\"m\")}",
1464
+ "gap={calc(plus(rem(0.25), pct(10)))}"
1465
+ ]
1466
+ },
1467
+ {
1468
+ "path": "flex",
1469
+ "grammar": "flex",
1470
+ "helpers": ["flex"],
1471
+ "shorthands": ["none", "auto", "initial"],
1472
+ "canonicalShorthands": ["none", "auto", "initial"],
1473
+ "exceptionalShorthands": [],
1474
+ "rawPolicy": "forbid",
1475
+ "examples": ["flex", "flex=\"none\"", "flex={flex(1, 1, rem(20))}"]
1476
+ }
1477
+ ],
1478
+ "boundaries": {
1479
+ "rootClass": "kui-row",
1480
+ "publicClasses": ["kui-row"],
1481
+ "publicTokens": ["--kui-layout-item-gap", "--kui-row-gap"]
1482
+ },
1483
+ "diagnostics": [],
1484
+ "provenance": {
1485
+ "selection": "ai/component-catalog.json",
1486
+ "composition": "docs/layout.md#row-and-list-alignment"
1487
+ }
1488
+ },
1404
1489
  {
1405
1490
  "key": "@kerfjs/ui:list",
1406
1491
  "package": "@kerfjs/ui",
@@ -1424,7 +1509,7 @@
1424
1509
  "required": false
1425
1510
  },
1426
1511
  {
1427
- "id": "typed gap, flex, and divider choices",
1512
+ "id": "typed gap, flex, alignment, and divider choices",
1428
1513
  "owner": "application",
1429
1514
  "required": false
1430
1515
  }
@@ -1636,13 +1636,108 @@
1636
1636
  "recipe": "docs/component-selection.md#problem-to-component-matrix"
1637
1637
  }
1638
1638
  },
1639
+ {
1640
+ "id": "row",
1641
+ "name": "Row",
1642
+ "category": "Structure",
1643
+ "kind": "component",
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.",
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.",
1648
+ "publicExports": ["Row"],
1649
+ "cssValueProps": [
1650
+ {
1651
+ "path": "gap",
1652
+ "grammar": "length",
1653
+ "helpers": ["px", "rem", "em", "pct", "space", "lengthVar", "calc"],
1654
+ "nonStandaloneHelpers": ["plus"],
1655
+ "shorthands": ["none", "2xs", "xs", "s", "m", "l", "xl"],
1656
+ "canonicalShorthands": ["none", "2xs", "xs", "m", "l"],
1657
+ "exceptionalShorthands": ["s", "xl"],
1658
+ "rawPolicy": "forbid",
1659
+ "examples": ["gap=\"xs\"", "gap={space(\"m\")}", "gap={calc(plus(rem(0.25), pct(10)))}"]
1660
+ },
1661
+ {
1662
+ "path": "flex",
1663
+ "grammar": "flex",
1664
+ "helpers": ["flex"],
1665
+ "shorthands": ["none", "auto", "initial"],
1666
+ "canonicalShorthands": ["none", "auto", "initial"],
1667
+ "exceptionalShorthands": [],
1668
+ "rawPolicy": "forbid",
1669
+ "examples": ["flex", "flex=\"none\"", "flex={flex(1, 1, rem(20))}"]
1670
+ }
1671
+ ],
1672
+ "useWhen": [
1673
+ "Arrange a group of sibling components horizontally.",
1674
+ "Choose physical horizontal distribution and vertical alignment without writing one-off flex CSS.",
1675
+ "Use a finite flex keyword or the flex() builder when Row participates in a flexible parent layout.",
1676
+ "Allow a bounded group to wrap while retaining the same typed gap and alignment contract."
1677
+ ],
1678
+ "avoidWhen": [
1679
+ "Do not use Row when content belongs in a semantic list or when a named-zone component such as Toolbar owns the composition.",
1680
+ "Do not pass raw CSS strings to gap or flex, mix their distinct grammars, or use s/xl spacing steps without a deliberate exception.",
1681
+ "Do not use alignment to replace semantic grouping or responsive relocation."
1682
+ ],
1683
+ "alternatives": [
1684
+ {
1685
+ "id": "list",
1686
+ "relationship": "axis-alternative",
1687
+ "when": "Use List when the main layout axis is vertical."
1688
+ },
1689
+ {
1690
+ "id": "toolbar",
1691
+ "relationship": "semantic-alternative",
1692
+ "when": "Use Toolbar when content belongs to named leading, center, and trailing application-chrome zones."
1693
+ }
1694
+ ],
1695
+ "appOwns": [
1696
+ "child semantics and content",
1697
+ "alignment and wrapping policy",
1698
+ "typed gap and flex choices"
1699
+ ],
1700
+ "variants": [
1701
+ "left/center/right/full horizontal alignment with l/c/r/f and flex aliases",
1702
+ "top/middle/bottom/full vertical alignment with t/m/c/b/f and flex aliases",
1703
+ "named none/2xs/xs/s/m/l/xl token gap",
1704
+ "typed CssLength gap",
1705
+ "boolean, finite-keyword, or typed CssFlex flex",
1706
+ "nowrap or wrapping"
1707
+ ],
1708
+ "accessibility": [
1709
+ "Row adds layout only and no grouping role; children retain responsibility for native or ARIA semantics.",
1710
+ "Wrapping must preserve a readable source and focus order."
1711
+ ],
1712
+ "geometry": {
1713
+ "margin": "none",
1714
+ "border": "none",
1715
+ "padding": "none",
1716
+ "notes": [
1717
+ "Row adds no margin, border, or padding; it owns only flex participation, alignment, wrapping, and the gap between children."
1718
+ ]
1719
+ },
1720
+ "delivery": {
1721
+ "browserImport": "@kerfjs/ui/row",
1722
+ "manualCssImport": "@kerfjs/ui/row.css",
1723
+ "sideEffects": ["browser-condition-css"]
1724
+ },
1725
+ "wiring": [],
1726
+ "publicClasses": ["kui-row"],
1727
+ "publicTokens": ["--kui-layout-item-gap", "--kui-row-gap"],
1728
+ "links": {
1729
+ "catalogRoute": "?component=row",
1730
+ "documentation": "docs/layout.md",
1731
+ "recipe": "docs/layout.md#row-and-list-alignment"
1732
+ }
1733
+ },
1639
1734
  {
1640
1735
  "id": "list",
1641
1736
  "name": "List",
1642
1737
  "category": "Navigation",
1643
1738
  "kind": "component",
1644
1739
  "source": "kerf",
1645
- "description": "A stretch-aligned vertical stack with optional typed token/length gap, flex growth, vertical scrolling, and independently selectable divider edges.",
1740
+ "description": "A vertical stack with physical-axis alignment, optional typed token/length gap, flex growth, vertical scrolling, and independently selectable divider edges.",
1646
1741
  "uses": [
1647
1742
  "list-header",
1648
1743
  "list-action-row",
@@ -1689,6 +1784,7 @@
1689
1784
  "Let one bounded list own vertical scrolling, flex growth, or one or more physical-edge dividers.",
1690
1785
  "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.",
1691
1786
  "Use a finite flex keyword or the flex() builder when List participates in flexible layout."
1787
+ ,"Align children horizontally or distribute them vertically with the same physical alignment vocabulary as Row."
1692
1788
  ],
1693
1789
  "avoidWhen": [
1694
1790
  "Do not use List as semantic list markup; use ul/ol when list semantics are required and apply layout deliberately.",
@@ -1699,7 +1795,7 @@
1699
1795
  "appOwns": [
1700
1796
  "child semantics and content",
1701
1797
  "the bounded height needed for scrolling",
1702
- "typed gap, flex, and divider choices"
1798
+ "typed gap, flex, alignment, and divider choices"
1703
1799
  ],
1704
1800
  "variants": [
1705
1801
  "no gap",
@@ -1708,6 +1804,8 @@
1708
1804
  "typed CssLength gap",
1709
1805
  "boolean, finite-keyword, or typed CssFlex flex",
1710
1806
  "scrollable",
1807
+ "left/center/right/full horizontal alignment",
1808
+ "top/middle/bottom/full vertical alignment",
1711
1809
  "top/right/bottom/left divider combinations"
1712
1810
  ],
1713
1811
  "accessibility": [
@@ -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.33` and `kerfjs@5.0.0-beta.33`. 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.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.
4
4
 
5
5
  ## `@kerfjs/ui/css-values`
6
6
 
@@ -333,6 +333,7 @@ export { ListHeader, type ListHeaderProps };
333
333
  import * as kerfjs from 'kerfjs';
334
334
  import { UiSpaceName, CssLength, CssFlexKeyword, CssFlex } from './css-values.js';
335
335
  import { D as DividerSides } from './divider-sides-267FA7sY.js';
336
+ import { H as HorizontalAlignment, V as VerticalAlignment } from './flex-alignment-CF8NLj7i.js';
336
337
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
337
338
 
338
339
  interface ListProps {
@@ -341,6 +342,10 @@ interface ListProps {
341
342
  gap?: boolean | UiSpaceName | CssLength;
342
343
  /** Allow this list to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */
343
344
  flex?: boolean | CssFlexKeyword | CssFlex;
345
+ /** Horizontal alignment. Defaults to full to preserve stretch-aligned list children. */
346
+ hAlign?: HorizontalAlignment;
347
+ /** Vertical distribution. Defaults to top. */
348
+ vAlign?: VerticalAlignment;
344
349
  /** Own vertical scrolling and overscroll containment. */
345
350
  scrollable?: boolean;
346
351
  /** Physical divider edges in canonical top/right/bottom/left order. */
@@ -348,9 +353,9 @@ interface ListProps {
348
353
  className?: string;
349
354
  }
350
355
  /** A stretch-aligned vertical stack with optional gap, flex, scroll, and dividers. */
351
- declare function List({ children, gap, flex, scrollable, dividerSides, className, }: ListProps): kerfjs.SafeHtml;
356
+ declare function List({ children, gap, flex, hAlign, vAlign, scrollable, dividerSides, className, }: ListProps): kerfjs.SafeHtml;
352
357
 
353
- export { CssFlex, CssFlexKeyword, CssLength, DividerSides, List, type ListProps, UiSpaceName };
358
+ export { CssFlex, CssFlexKeyword, CssLength, DividerSides, HorizontalAlignment, List, type ListProps, UiSpaceName, VerticalAlignment };
354
359
  ```
355
360
 
356
361
  ## `@kerfjs/ui/list-action-row`
@@ -1935,3 +1940,81 @@ declare function delegateActions<E extends Element = Element>(root: HTMLElement,
1935
1940
 
1936
1941
  export { type ActionHandler, type DelegateActionsOptions, action, delegateActions };
1937
1942
  ```
1943
+
1944
+ ## `@kerfjs/ui/lucide-icon`
1945
+
1946
+ ```ts
1947
+ import * as kerfjs from 'kerfjs';
1948
+ import { IconNode } from 'lucide';
1949
+
1950
+ type LucideNode = IconNode;
1951
+ interface LucideIconProps {
1952
+ icon: LucideNode;
1953
+ name: string;
1954
+ className?: string;
1955
+ label?: string;
1956
+ }
1957
+ /** Render a Lucide-compatible icon node without copying icon SVG strings. */
1958
+ declare function LucideIcon({ icon, name, className, label }: LucideIconProps): kerfjs.SafeHtml;
1959
+
1960
+ export { LucideIcon, type LucideIconProps, type LucideNode };
1961
+ ```
1962
+
1963
+ ## `@kerfjs/ui/surface-scaffold`
1964
+
1965
+ ```ts
1966
+ import * as kerfjs from 'kerfjs';
1967
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
1968
+
1969
+ type DialogSurfaceSize = 'small' | 'medium' | 'large';
1970
+ type DialogSurfacePresentation = 'modal' | 'side-sheet' | 'fullscreen';
1971
+ type SurfaceInset = 'none' | 'compact' | 'comfortable';
1972
+ interface DialogSurfaceProps {
1973
+ children: KerfUiContent;
1974
+ size?: DialogSurfaceSize;
1975
+ presentation?: DialogSurfacePresentation;
1976
+ bodyInset?: SurfaceInset;
1977
+ footerInset?: SurfaceInset;
1978
+ className?: string;
1979
+ }
1980
+ /** Configure recurring Web Awesome dialog geometry without consumer ::part() CSS. */
1981
+ declare function DialogSurface({ children, size, presentation, bodyInset, footerInset, className, }: DialogSurfaceProps): kerfjs.SafeHtml;
1982
+ type PopupSurfaceInset = 'standard' | 'compact' | 'list-zero';
1983
+ interface PopupSurfaceProps {
1984
+ children: KerfUiContent;
1985
+ inset?: PopupSurfaceInset;
1986
+ className?: string;
1987
+ }
1988
+ /** Configure recurring Web Awesome dropdown-menu geometry without consumer ::part() CSS. */
1989
+ declare function PopupSurface({ children, inset, className, }: PopupSurfaceProps): kerfjs.SafeHtml;
1990
+
1991
+ export { DialogSurface, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceSize, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset };
1992
+ ```
1993
+
1994
+ ## `@kerfjs/ui/row`
1995
+
1996
+ ```ts
1997
+ import * as kerfjs from 'kerfjs';
1998
+ import { UiSpaceName, CssLength, CssFlexKeyword, CssFlex } from './css-values.js';
1999
+ import { H as HorizontalAlignment, V as VerticalAlignment } from './flex-alignment-CF8NLj7i.js';
2000
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
2001
+
2002
+ interface RowProps {
2003
+ children?: KerfUiContent;
2004
+ /** Horizontal distribution. Defaults to left. */
2005
+ hAlign?: HorizontalAlignment;
2006
+ /** Vertical alignment and wrapped-line distribution. Defaults to full. */
2007
+ vAlign?: VerticalAlignment;
2008
+ /** A named UI spacing token or typed CSS length. Defaults to xs. */
2009
+ gap?: UiSpaceName | CssLength;
2010
+ /** Allow this row to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */
2011
+ flex?: boolean | CssFlexKeyword | CssFlex;
2012
+ /** Allow children to wrap onto additional lines. */
2013
+ wrap?: boolean;
2014
+ className?: string;
2015
+ }
2016
+ /** 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;
2018
+
2019
+ export { CssFlex, CssFlexKeyword, CssLength, HorizontalAlignment, Row, type RowProps, UiSpaceName, VerticalAlignment };
2020
+ ```
package/ai/skill.md CHANGED
@@ -78,6 +78,7 @@ Quick routing:
78
78
  | Need | Choose | Nearest alternatives / boundary |
79
79
  | ------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
80
80
  | Navigation row | `ListItem` | Use an ordinary link or button when sidebar/menu anatomy and state do not apply. Its rounded root clips overflowing descendants; set `multiline` when the primary label should wrap. Multiline leading icons stay aligned with the first text line. |
81
+ | Horizontal flex layout | `Row` | Use `hAlign`/`vAlign`, a typed `gap`, and opt-in `wrap`; use `List` when the main axis is vertical. |
81
82
  | Navigation row with a trailing action | `ListActionRow` | Use `ListItem` when the trailing region is dormant; never put controls inside either component's SafeHtml slots. Multiline leading icons stay aligned with the first text line. |
82
83
  | Page, panel, or dialog heading | `Toolbar` + `ToolbarText` | Use a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. |
83
84
  | Exclusive choice | `TabBar`, `SegmentedControl`, or `Select` | Tabs switch tabpanels; segments expose a few choices; Select handles a longer value list. |
@@ -127,13 +128,13 @@ Hard rules:
127
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.
128
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.
129
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.
130
- 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 `List` instead of a hand-written flex-column wrapper when rows or sections need stretch alignment, standard/custom gap, 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` 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
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.
132
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
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.
134
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.
135
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.
136
- 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 `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"`; 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
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`.
138
139
 
139
140
  Common mistakes:
@@ -1,6 +1,6 @@
1
1
  # Web Awesome JSX signatures for the UI authoring corpus
2
2
 
3
- Generated from the emitted `@kerfjs/ui@5.0.0-beta.33` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
3
+ Generated from the emitted `@kerfjs/ui@5.0.0-beta.35` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
4
4
 
5
5
  ```ts
6
6
  import { KerfCustomElement } from 'kerfjs/jsx-runtime';
@@ -0,0 +1,3 @@
1
+ import '../styles/foundation.css';
2
+ import '../styles/row.css';
3
+ export * from '../row.js';
package/dist/catalog.js CHANGED
@@ -3,7 +3,8 @@ import { Toolbar } from './chunk-VXFGMDA7.js';
3
3
  import { Pane } from './chunk-I4V3UDSA.js';
4
4
  import { ListHeader } from './chunk-OC275HHS.js';
5
5
  import { ListItem } from './chunk-I45QO2IZ.js';
6
- import { List } from './chunk-RL6COPKF.js';
6
+ import { List } from './chunk-7JQAQWVC.js';
7
+
7
8
  import { filterDataAttributes } from './chunk-SRSJO5QE.js';
8
9
 
9
10