@kerfjs/ui 5.0.0-beta.104 → 5.0.0-beta.105
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 +155 -116
- package/ai/application-ui-profile.defaults.json +125 -4
- package/ai/compile-time-contracts-v1.json +11 -11
- package/ai/component-catalog.json +2606 -628
- package/ai/component-composition-rules.json +10 -10
- package/ai/component-composition.json +1688 -300
- package/ai/components/README.md +66 -48
- package/ai/components/application-tabs.md +4 -4
- package/ai/components/badge.md +1 -0
- package/ai/components/button.md +91 -0
- package/ai/components/checkbox.md +89 -0
- package/ai/components/choice-chips.md +9 -3
- package/ai/components/color-field.md +92 -0
- package/ai/components/color-picker.md +90 -0
- package/ai/components/color-swatches.md +82 -0
- package/ai/components/combobox.md +6 -4
- package/ai/components/content-item.md +5 -2
- package/ai/components/disclosure.md +83 -0
- package/ai/components/file-picker.md +1 -0
- package/ai/components/foundation.md +1 -1
- package/ai/components/list-action-row.md +3 -3
- package/ai/components/list-detail-dialog.md +90 -0
- package/ai/components/list.md +2 -0
- package/ai/components/lucide-icon.md +2 -0
- package/ai/components/{composer.md → message-composer.md} +17 -16
- package/ai/components/pane.md +3 -1
- package/ai/components/popup-menu.md +12 -9
- package/ai/components/progress-bar.md +82 -0
- package/ai/components/progress-ring.md +80 -0
- package/ai/components/radio-group.md +88 -0
- package/ai/components/recipe-list-detail-dialog.md +5 -5
- package/ai/components/{token-search-field.md → search-field.md} +23 -23
- package/ai/components/select.md +15 -13
- package/ai/components/slider.md +84 -0
- package/ai/components/surface-scaffold.md +16 -13
- package/ai/components/switch.md +81 -0
- package/ai/components/tab-bar.md +6 -6
- package/ai/components/tab-navigator.md +2 -2
- package/ai/components/tabs.md +7 -7
- package/ai/components/tag-field.md +86 -0
- package/ai/components/text-field.md +98 -0
- package/ai/components/text.md +1 -0
- package/ai/components/toast.md +85 -0
- package/ai/components/toolbar-control-group.md +3 -3
- package/ai/components/tooltip.md +81 -0
- package/ai/components/wa-accordion-item.md +11 -5
- package/ai/components/wa-accordion.md +11 -5
- package/ai/components/wa-animated-image.md +2 -1
- package/ai/components/wa-animation.md +1 -0
- package/ai/components/wa-avatar.md +1 -0
- package/ai/components/wa-badge.md +10 -3
- package/ai/components/wa-breadcrumb-item.md +1 -0
- package/ai/components/wa-breadcrumb.md +1 -0
- package/ai/components/wa-button-group.md +6 -4
- package/ai/components/wa-button.md +7 -5
- package/ai/components/wa-callout.md +7 -5
- package/ai/components/wa-card.md +12 -4
- package/ai/components/wa-carousel-item.md +1 -0
- package/ai/components/wa-carousel.md +1 -0
- package/ai/components/wa-checkbox-group.md +11 -4
- package/ai/components/wa-checkbox.md +10 -4
- package/ai/components/wa-color-picker.md +13 -5
- package/ai/components/wa-comparison.md +2 -1
- package/ai/components/wa-copy-button.md +1 -0
- package/ai/components/wa-details.md +11 -5
- package/ai/components/wa-dialog.md +10 -4
- package/ai/components/wa-divider.md +1 -0
- package/ai/components/wa-drawer.md +12 -5
- package/ai/components/wa-dropdown-item.md +8 -6
- package/ai/components/wa-dropdown.md +8 -6
- package/ai/components/wa-format-bytes.md +1 -0
- package/ai/components/wa-format-date.md +1 -0
- package/ai/components/wa-format-number.md +1 -0
- package/ai/components/wa-icon.md +2 -1
- package/ai/components/wa-include.md +6 -4
- package/ai/components/wa-input.md +9 -6
- package/ai/components/wa-intersection-observer.md +1 -0
- package/ai/components/wa-known-date.md +1 -0
- package/ai/components/wa-markdown.md +1 -0
- package/ai/components/wa-mutation-observer.md +1 -0
- package/ai/components/wa-number-input.md +1 -0
- package/ai/components/wa-option.md +7 -5
- package/ai/components/wa-otp-input.md +1 -0
- package/ai/components/wa-page.md +13 -4
- package/ai/components/wa-pagination.md +1 -0
- package/ai/components/wa-popover.md +11 -5
- package/ai/components/wa-popup.md +11 -4
- package/ai/components/wa-progress-bar.md +11 -4
- package/ai/components/wa-progress-ring.md +11 -4
- package/ai/components/wa-qr-code.md +1 -0
- package/ai/components/wa-radio-group.md +12 -4
- package/ai/components/wa-radio.md +10 -4
- package/ai/components/wa-random-content.md +1 -0
- package/ai/components/wa-rating.md +1 -0
- package/ai/components/wa-relative-time.md +1 -0
- package/ai/components/wa-resize-observer.md +1 -0
- package/ai/components/wa-scroller.md +12 -4
- package/ai/components/wa-select.md +7 -5
- package/ai/components/wa-skeleton.md +10 -3
- package/ai/components/wa-slider.md +11 -5
- package/ai/components/wa-spinner.md +7 -5
- package/ai/components/wa-split-panel.md +7 -5
- package/ai/components/wa-step.md +56 -0
- package/ai/components/wa-stepper.md +56 -0
- package/ai/components/wa-switch.md +10 -3
- package/ai/components/wa-tab-group.md +8 -6
- package/ai/components/wa-tab-panel.md +7 -5
- package/ai/components/wa-tab.md +8 -6
- package/ai/components/wa-tag-input.md +62 -0
- package/ai/components/wa-tag.md +9 -6
- package/ai/components/wa-textarea.md +10 -3
- package/ai/components/wa-time-input.md +1 -0
- package/ai/components/wa-toast-item.md +9 -7
- package/ai/components/wa-toast.md +9 -7
- package/ai/components/wa-tooltip.md +11 -5
- package/ai/components/wa-tree-item.md +2 -1
- package/ai/components/wa-tree.md +2 -1
- package/ai/components/wa-zoomable-frame.md +2 -1
- package/ai/components/workbench-dialog.md +90 -0
- package/ai/public-api-signatures-v1.md +1577 -245
- package/ai/skill.md +83 -50
- package/ai/webawesome-jsx-signatures-v1.md +4 -1
- package/dist/badge.d.ts +9 -1
- package/dist/badge.js +1 -1
- package/dist/browser/button.js +5 -0
- package/dist/browser/catalog.js +3 -2
- package/dist/browser/{icon-button.js → checkbox.js} +2 -2
- package/dist/browser/color-field.js +4 -0
- package/dist/browser/color-picker.js +5 -0
- package/dist/browser/color-swatches.js +3 -0
- package/dist/browser/disclosure.js +5 -0
- package/dist/browser/list-action-row.js +1 -0
- package/dist/browser/list-detail-dialog.js +14 -0
- package/dist/browser/message-composer.js +5 -0
- package/dist/browser/popup-menu.js +1 -0
- package/dist/browser/progress-bar.js +3 -0
- package/dist/browser/progress-ring.js +3 -0
- package/dist/browser/radio-group.js +3 -0
- package/dist/browser/search-field.js +4 -0
- package/dist/browser/slider.js +3 -0
- package/dist/browser/surface-scaffold.js +1 -0
- package/dist/browser/switch.js +3 -0
- package/dist/browser/tab-bar-item.js +4 -0
- package/dist/browser/tag-field.js +5 -0
- package/dist/browser/text-field.js +4 -0
- package/dist/browser/workbench-dialog.js +15 -0
- package/dist/button.d.ts +108 -0
- package/dist/button.js +7 -0
- package/dist/button.js.map +1 -0
- package/dist/catalog-resources.d.ts +1 -1
- package/dist/catalog.d.ts +3 -3
- package/dist/catalog.js +57 -27
- package/dist/catalog.js.map +1 -1
- package/dist/checkbox.d.ts +45 -0
- package/dist/checkbox.js +6 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/choice-chips.d.ts +49 -15
- package/dist/choice-chips.js +1 -1
- package/dist/chunk-23PPPNH6.js +93 -0
- package/dist/chunk-23PPPNH6.js.map +1 -0
- package/dist/{chunk-FNZQOUZ5.js → chunk-2FW7D4IG.js} +2 -2
- package/dist/{chunk-FNZQOUZ5.js.map → chunk-2FW7D4IG.js.map} +1 -1
- package/dist/{chunk-ZYU5WRC3.js → chunk-3GLYIIFR.js} +2 -2
- package/dist/{chunk-ZYU5WRC3.js.map → chunk-3GLYIIFR.js.map} +1 -1
- package/dist/{chunk-5NQF35VO.js → chunk-4RECPEHI.js} +2 -2
- package/dist/{chunk-5NQF35VO.js.map → chunk-4RECPEHI.js.map} +1 -1
- package/dist/{chunk-7IUEBNCO.js → chunk-5USKCWGL.js} +8 -3
- package/dist/chunk-5USKCWGL.js.map +1 -0
- package/dist/chunk-6VVIW7VB.js +69 -0
- package/dist/chunk-6VVIW7VB.js.map +1 -0
- package/dist/{chunk-LL45C7BB.js → chunk-7K6U6FCN.js} +1 -1
- package/dist/chunk-7K6U6FCN.js.map +1 -0
- package/dist/{chunk-HQZYKJ56.js → chunk-7W3GKMN4.js} +110 -74
- package/dist/chunk-7W3GKMN4.js.map +1 -0
- package/dist/chunk-ACUKTVS3.js +371 -0
- package/dist/chunk-ACUKTVS3.js.map +1 -0
- package/dist/{chunk-YQ2AC4E2.js → chunk-AMIHGFLP.js} +2 -2
- package/dist/{chunk-YQ2AC4E2.js.map → chunk-AMIHGFLP.js.map} +1 -1
- package/dist/chunk-ATGAJVZ6.js +268 -0
- package/dist/chunk-ATGAJVZ6.js.map +1 -0
- package/dist/chunk-AYPGXR2E.js +20 -0
- package/dist/chunk-AYPGXR2E.js.map +1 -0
- package/dist/{chunk-ETCWQLJV.js → chunk-BKUMG2KB.js} +38 -28
- package/dist/chunk-BKUMG2KB.js.map +1 -0
- package/dist/chunk-C26Q6P4Q.js +76 -0
- package/dist/chunk-C26Q6P4Q.js.map +1 -0
- package/dist/chunk-C3RLKKFQ.js +452 -0
- package/dist/chunk-C3RLKKFQ.js.map +1 -0
- package/dist/{chunk-T5BJBMW7.js → chunk-CIAUILRR.js} +5 -5
- package/dist/chunk-CIAUILRR.js.map +1 -0
- package/dist/chunk-CNM6ZWP2.js +38 -0
- package/dist/chunk-CNM6ZWP2.js.map +1 -0
- package/dist/{chunk-LPHU44S3.js → chunk-CTQUDCY5.js} +3 -1
- package/dist/chunk-CTQUDCY5.js.map +1 -0
- package/dist/{chunk-MXNIVEVL.js → chunk-D4MXSAMI.js} +5 -3
- package/dist/chunk-D4MXSAMI.js.map +1 -0
- package/dist/{chunk-YBWQR2OK.js → chunk-DDSCVEO5.js} +10 -9
- package/dist/chunk-DDSCVEO5.js.map +1 -0
- package/dist/{chunk-SIW64V65.js → chunk-DF2YKGIX.js} +3 -3
- package/dist/{chunk-SIW64V65.js.map → chunk-DF2YKGIX.js.map} +1 -1
- package/dist/{chunk-3SKAEPPZ.js → chunk-ECFCAKWE.js} +4 -4
- package/dist/{chunk-3SKAEPPZ.js.map → chunk-ECFCAKWE.js.map} +1 -1
- package/dist/chunk-EKBMUXFV.js +298 -0
- package/dist/chunk-EKBMUXFV.js.map +1 -0
- package/dist/{chunk-IXLUZHGT.js → chunk-FG6U7KQQ.js} +5 -5
- package/dist/{chunk-IXLUZHGT.js.map → chunk-FG6U7KQQ.js.map} +1 -1
- package/dist/chunk-FKKJHGN5.js +147 -0
- package/dist/chunk-FKKJHGN5.js.map +1 -0
- package/dist/{chunk-FDZNYD4C.js → chunk-GCAK23PU.js} +34 -3
- package/dist/chunk-GCAK23PU.js.map +1 -0
- package/dist/chunk-GXSX6Q6O.js +42 -0
- package/dist/chunk-GXSX6Q6O.js.map +1 -0
- package/dist/{chunk-LOSRXZAH.js → chunk-HJXVVFBT.js} +14 -6
- package/dist/chunk-HJXVVFBT.js.map +1 -0
- package/dist/{chunk-M5KZ26VV.js → chunk-HQZG3OFL.js} +11 -1
- package/dist/chunk-HQZG3OFL.js.map +1 -0
- package/dist/{chunk-ELCMSQNJ.js → chunk-HTERKONR.js} +8 -8
- package/dist/chunk-HTERKONR.js.map +1 -0
- package/dist/chunk-HYORKKYP.js +8 -0
- package/dist/chunk-HYORKKYP.js.map +1 -0
- package/dist/chunk-IO27YTL3.js +73 -0
- package/dist/chunk-IO27YTL3.js.map +1 -0
- package/dist/chunk-ITUQC4FW.js +163 -0
- package/dist/chunk-ITUQC4FW.js.map +1 -0
- package/dist/chunk-K57NINLH.js +137 -0
- package/dist/chunk-K57NINLH.js.map +1 -0
- package/dist/chunk-KC6WUSNS.js +34 -0
- package/dist/chunk-KC6WUSNS.js.map +1 -0
- package/dist/{chunk-W4IK7AWD.js → chunk-KDQZ3E5C.js} +3 -1
- package/dist/chunk-KDQZ3E5C.js.map +1 -0
- package/dist/{chunk-2MSPNRWJ.js → chunk-KK3S5OTW.js} +27 -11
- package/dist/chunk-KK3S5OTW.js.map +1 -0
- package/dist/chunk-LTBVP6VN.js +59 -0
- package/dist/chunk-LTBVP6VN.js.map +1 -0
- package/dist/chunk-OHPYT2BH.js +82 -0
- package/dist/chunk-OHPYT2BH.js.map +1 -0
- package/dist/{chunk-HZPHCSCS.js → chunk-OY2MXIBB.js} +8 -2
- package/dist/chunk-OY2MXIBB.js.map +1 -0
- package/dist/chunk-PE2KPZDW.js +85 -0
- package/dist/chunk-PE2KPZDW.js.map +1 -0
- package/dist/{chunk-QTSMHH5J.js → chunk-QPTJ5DOO.js} +5 -3
- package/dist/chunk-QPTJ5DOO.js.map +1 -0
- package/dist/{chunk-JOR5LGYE.js → chunk-RFLW4FPL.js} +1 -1
- package/dist/chunk-RFLW4FPL.js.map +1 -0
- package/dist/{chunk-U6QYTMDB.js → chunk-RMWNO6BA.js} +6 -2
- package/dist/chunk-RMWNO6BA.js.map +1 -0
- package/dist/{chunk-TDMHA7C3.js → chunk-SQ2Q3OQI.js} +112 -63
- package/dist/chunk-SQ2Q3OQI.js.map +1 -0
- package/dist/{chunk-K4AT3V3J.js → chunk-T74YB76U.js} +13 -11
- package/dist/chunk-T74YB76U.js.map +1 -0
- package/dist/{chunk-BYFTHA4I.js → chunk-TAHR457X.js} +53 -6
- package/dist/chunk-TAHR457X.js.map +1 -0
- package/dist/{chunk-3VXDSIBJ.js → chunk-TKNVL53O.js} +2 -2
- package/dist/{chunk-3VXDSIBJ.js.map → chunk-TKNVL53O.js.map} +1 -1
- package/dist/chunk-TNJLNLVE.js +115 -0
- package/dist/chunk-TNJLNLVE.js.map +1 -0
- package/dist/chunk-TUID42UE.js +15 -0
- package/dist/chunk-TUID42UE.js.map +1 -0
- package/dist/{chunk-CR5U5FV6.js → chunk-UMNHJSIO.js} +93 -80
- package/dist/chunk-UMNHJSIO.js.map +1 -0
- package/dist/chunk-UTCAOOXU.js +116 -0
- package/dist/chunk-UTCAOOXU.js.map +1 -0
- package/dist/{chunk-ZJC5ND3M.js → chunk-VEME3D7K.js} +35 -35
- package/dist/chunk-VEME3D7K.js.map +1 -0
- package/dist/{chunk-HLDRH23D.js → chunk-WJKHLOP5.js} +2 -2
- package/dist/{chunk-HLDRH23D.js.map → chunk-WJKHLOP5.js.map} +1 -1
- package/dist/chunk-WU57BYYM.js +115 -0
- package/dist/chunk-WU57BYYM.js.map +1 -0
- package/dist/{chunk-TGLUDQP2.js → chunk-ZCDRLN3B.js} +11 -11
- package/dist/chunk-ZCDRLN3B.js.map +1 -0
- package/dist/chunk-ZX2QQJXD.js +249 -0
- package/dist/chunk-ZX2QQJXD.js.map +1 -0
- package/dist/{chunk-6E2H2E5A.js → chunk-ZXZAECHZ.js} +6 -6
- package/dist/{chunk-6E2H2E5A.js.map → chunk-ZXZAECHZ.js.map} +1 -1
- package/dist/collapsible-panel.js +3 -3
- package/dist/color-field.d.ts +51 -0
- package/dist/color-field.js +92 -0
- package/dist/color-field.js.map +1 -0
- package/dist/color-picker.d.ts +88 -0
- package/dist/color-picker.js +8 -0
- package/dist/color-picker.js.map +1 -0
- package/dist/color-swatches.d.ts +56 -0
- package/dist/color-swatches.js +4 -0
- package/dist/color-swatches.js.map +1 -0
- package/dist/combobox.js +1 -1
- package/dist/content-item.d.ts +13 -1
- package/dist/content-item.js +1 -1
- package/dist/disclosure-arrow.js +1 -1
- package/dist/disclosure.d.ts +57 -0
- package/dist/disclosure.js +51 -0
- package/dist/disclosure.js.map +1 -0
- package/dist/file-picker.d.ts +7 -0
- package/dist/file-picker.js +1 -1
- package/dist/help-tags-register.d.ts +8 -8
- package/dist/help-tags-register.js +3 -2
- package/dist/help-tags-register.js.map +1 -1
- package/dist/index.d.ts +16 -9
- package/dist/index.js +58 -46
- package/dist/list-action-row.js +4 -1
- package/dist/list-detail-dialog.d.ts +117 -0
- package/dist/list-detail-dialog.js +165 -0
- package/dist/list-detail-dialog.js.map +1 -0
- package/dist/list-header.js +1 -1
- package/dist/list-item.js +1 -1
- package/dist/list.d.ts +17 -2
- package/dist/list.js +1 -1
- package/dist/lucide-icon.d.ts +9 -1
- package/dist/lucide-icon.js +1 -1
- package/dist/{composer.d.ts → message-composer.d.ts} +20 -12
- package/dist/message-composer.js +6 -0
- package/dist/message-composer.js.map +1 -0
- package/dist/nav-stack.js +1 -1
- package/dist/pane.d.ts +36 -3
- package/dist/pane.js +1 -1
- package/dist/popup-menu-register.d.ts +6 -5
- package/dist/popup-menu-register.js +3 -326
- package/dist/popup-menu-register.js.map +1 -1
- package/dist/popup-menu.d.ts +41 -23
- package/dist/popup-menu.js +5 -1
- package/dist/progress-bar.d.ts +52 -0
- package/dist/progress-bar.js +68 -0
- package/dist/progress-bar.js.map +1 -0
- package/dist/progress-ring.d.ts +44 -0
- package/dist/progress-ring.js +73 -0
- package/dist/progress-ring.js.map +1 -0
- package/dist/radio-group.d.ts +62 -0
- package/dist/radio-group.js +4 -0
- package/dist/radio-group.js.map +1 -0
- package/dist/row.js +1 -1
- package/dist/{token-search-model-JA1i8z4D.d.ts → search-field-CrGTN5sa.d.ts} +45 -45
- package/dist/search-field-model.d.ts +3 -0
- package/dist/search-field-model.js +3 -0
- package/dist/search-field-model.js.map +1 -0
- package/dist/search-field.d.ts +3 -0
- package/dist/search-field.js +5 -0
- package/dist/search-field.js.map +1 -0
- package/dist/select-register.d.ts +6 -3
- package/dist/select-register.js +383 -241
- package/dist/select-register.js.map +1 -1
- package/dist/select.d.ts +30 -6
- package/dist/select.js +3 -1
- package/dist/selection.d.ts +54 -0
- package/dist/selection.js +3 -0
- package/dist/selection.js.map +1 -0
- package/dist/slider.d.ts +74 -0
- package/dist/slider.js +103 -0
- package/dist/slider.js.map +1 -0
- package/dist/split-button.js +1 -1
- package/dist/split-view.d.ts +5 -2
- package/dist/split-view.js +18 -159
- package/dist/split-view.js.map +1 -1
- package/dist/state-banner.js +1 -1
- package/dist/styles/badge.css +18 -6
- package/dist/styles/button.css +230 -0
- package/dist/styles/catalog/components/catalog-example.css +13 -4
- package/dist/styles/checkbox.css +121 -0
- package/dist/styles/choice-chips.css +58 -7
- package/dist/styles/color-field.css +125 -0
- package/dist/styles/color-picker.css +331 -0
- package/dist/styles/color-swatches.css +122 -0
- package/dist/styles/combobox.css +7 -5
- package/dist/styles/components/actions/button/button.css +230 -0
- package/dist/styles/components/actions/popup-menu/popup-menu.css +326 -198
- package/dist/styles/components/actions/toolbar/toolbar.css +10 -3
- package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +67 -104
- package/dist/styles/components/actions/toolbar-text/toolbar-text.css +5 -18
- package/dist/styles/components/collections/list/list.css +37 -0
- package/dist/styles/components/feedback/badge/badge.css +18 -6
- package/dist/styles/components/feedback/progress-bar/progress-bar.css +168 -0
- package/dist/styles/components/feedback/progress-ring/progress-ring.css +91 -0
- package/dist/styles/components/feedback/state-banner/state-banner.css +18 -3
- package/dist/styles/components/forms/checkbox/checkbox.css +121 -0
- package/dist/styles/components/forms/choice-chips/choice-chips.css +58 -7
- package/dist/styles/components/forms/color-field/color-field.css +125 -0
- package/dist/styles/components/forms/color-picker/color-picker.css +331 -0
- package/dist/styles/components/forms/color-swatches/color-swatches.css +122 -0
- package/dist/styles/components/forms/combobox/combobox.css +7 -5
- package/dist/styles/components/forms/{composer/composer.css → message-composer/message-composer.css} +17 -33
- package/dist/styles/components/forms/radio-group/radio-group.css +156 -0
- package/dist/styles/{token-search-field.css → components/forms/search-field/search-field.css} +111 -102
- package/dist/styles/components/forms/select/select.css +370 -249
- package/dist/styles/components/forms/slider/slider.css +241 -0
- package/dist/styles/components/forms/switch/switch.css +132 -0
- package/dist/styles/components/forms/tag-field/tag-field.css +8 -0
- package/dist/styles/components/forms/text-field/text-field.css +193 -0
- package/dist/styles/components/layout/pane/pane.css +254 -0
- package/dist/styles/components/layout/resizable-region/resizable-region.css +3 -2
- package/dist/styles/components/layout/row/row.css +11 -0
- package/dist/styles/components/layout/split-view/split-view.css +25 -0
- package/dist/styles/components/layout/workbench/workbench.css +12 -4
- package/dist/styles/components/navigation/disclosure-arrow/disclosure-arrow.css +20 -0
- package/dist/styles/components/navigation/tab-bar/tab-bar.css +7 -7
- package/dist/styles/components/navigation/{app-tab/app-tab.css → tab-bar-item/tab-bar-item.css} +95 -90
- package/dist/styles/components/surfaces/disclosure/disclosure.css +98 -0
- package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +263 -116
- package/dist/styles/components/typography/text/text.css +14 -12
- package/dist/styles/disclosure-arrow.css +20 -0
- package/dist/styles/disclosure.css +98 -0
- package/dist/styles/foundation.css +6 -4
- package/dist/styles/list-detail-dialog.css +11 -0
- package/dist/styles/list.css +37 -0
- package/dist/styles/{composer.css → message-composer.css} +17 -33
- package/dist/styles/overlay-toast.css +149 -0
- package/dist/styles/overlay-tooltip.css +55 -0
- package/dist/styles/pane.css +254 -0
- package/dist/styles/popup-menu.css +326 -198
- package/dist/styles/progress-bar.css +168 -0
- package/dist/styles/progress-ring.css +91 -0
- package/dist/styles/radio-group.css +156 -0
- package/dist/styles/resizable-region.css +3 -2
- package/dist/styles/row.css +11 -0
- package/dist/styles/{components/forms/token-search-field/token-search-field.css → search-field.css} +111 -102
- package/dist/styles/select.css +370 -249
- package/dist/styles/slider.css +241 -0
- package/dist/styles/split-view.css +25 -0
- package/dist/styles/state-banner.css +18 -3
- package/dist/styles/styles.css +16 -5
- package/dist/styles/surface-scaffold.css +263 -116
- package/dist/styles/switch.css +132 -0
- package/dist/styles/{app-tab.css → tab-bar-item.css} +95 -90
- package/dist/styles/tab-bar.css +7 -7
- package/dist/styles/tag-field.css +8 -0
- package/dist/styles/text-field.css +193 -0
- package/dist/styles/text.css +14 -12
- package/dist/styles/toolbar-control-group.css +67 -104
- package/dist/styles/toolbar-text.css +5 -18
- package/dist/styles/toolbar.css +10 -3
- package/dist/styles/webawesome.css +9 -0
- package/dist/styles/workbench-dialog.css +10 -0
- package/dist/styles/workbench.css +12 -4
- package/dist/surface-scaffold-register.d.ts +7 -5
- package/dist/surface-scaffold-register.js +2 -246
- package/dist/surface-scaffold-register.js.map +1 -1
- package/dist/surface-scaffold.d.ts +70 -7
- package/dist/surface-scaffold.js +5 -1
- package/dist/switch.d.ts +38 -0
- package/dist/switch.js +4 -0
- package/dist/switch.js.map +1 -0
- package/dist/{app-tab.d.ts → tab-bar-item.d.ts} +11 -11
- package/dist/tab-bar-item.js +6 -0
- package/dist/tab-bar-item.js.map +1 -0
- package/dist/tab-bar.d.ts +3 -3
- package/dist/tab-bar.js +1 -1
- package/dist/tab-navigator.js +1 -1
- package/dist/tag-field.d.ts +52 -0
- package/dist/tag-field.js +6 -0
- package/dist/tag-field.js.map +1 -0
- package/dist/text-field.d.ts +83 -0
- package/dist/text-field.js +6 -0
- package/dist/text-field.js.map +1 -0
- package/dist/text.d.ts +6 -1
- package/dist/text.js +1 -1
- package/dist/{types-CVgIwSdq.d.ts → types-DmHWkSd6.d.ts} +19 -1
- package/dist/webawesome.d.ts +3 -0
- package/dist/wire-catalog.d.ts +8 -2
- package/dist/wire-catalog.js +16 -4
- package/dist/wire-catalog.js.map +1 -1
- package/dist/wire-checkboxes.d.ts +10 -0
- package/dist/wire-checkboxes.js +3 -0
- package/dist/{wire-composers.js.map → wire-checkboxes.js.map} +1 -1
- package/dist/wire-choice-chips.d.ts +17 -0
- package/dist/wire-choice-chips.js +4 -0
- package/dist/{wire-token-search-fields.js.map → wire-choice-chips.js.map} +1 -1
- package/dist/wire-color-fields.d.ts +33 -0
- package/dist/wire-color-fields.js +82 -0
- package/dist/wire-color-fields.js.map +1 -0
- package/dist/wire-color-pickers.d.ts +34 -0
- package/dist/wire-color-pickers.js +9 -0
- package/dist/wire-color-pickers.js.map +1 -0
- package/dist/wire-comboboxes.js +2 -1
- package/dist/wire-content-items.d.ts +5 -1
- package/dist/wire-content-items.js +84 -15
- package/dist/wire-content-items.js.map +1 -1
- package/dist/wire-file-pickers.d.ts +3 -2
- package/dist/wire-file-pickers.js +1 -1
- package/dist/{wire-composers.d.ts → wire-message-composers.d.ts} +5 -4
- package/dist/wire-message-composers.js +3 -0
- package/dist/wire-message-composers.js.map +1 -0
- package/dist/wire-overlay-sheets.js +1 -1
- package/dist/wire-pane-chrome.d.ts +21 -0
- package/dist/wire-pane-chrome.js +67 -0
- package/dist/wire-pane-chrome.js.map +1 -0
- package/dist/{wire-token-search-fields.d.ts → wire-search-fields.d.ts} +25 -25
- package/dist/wire-search-fields.js +6 -0
- package/dist/wire-search-fields.js.map +1 -0
- package/dist/wire-sliders.d.ts +12 -0
- package/dist/wire-sliders.js +31 -0
- package/dist/wire-sliders.js.map +1 -0
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-tag-fields.d.ts +41 -0
- package/dist/wire-tag-fields.js +258 -0
- package/dist/wire-tag-fields.js.map +1 -0
- package/dist/wire-text-fields.d.ts +12 -0
- package/dist/wire-text-fields.js +3 -0
- package/dist/wire-text-fields.js.map +1 -0
- package/dist/workbench-dialog.d.ts +97 -0
- package/dist/workbench-dialog.js +124 -0
- package/dist/workbench-dialog.js.map +1 -0
- package/dist/workbench.d.ts +5 -0
- package/dist/workbench.js +1 -1
- package/docs/accessibility.md +181 -97
- package/docs/anchored-surface.md +8 -7
- package/docs/app-layouts.md +28 -0
- package/docs/catalog.md +14 -5
- package/docs/color-field.md +65 -0
- package/docs/color-picker.md +71 -0
- package/docs/color-swatches.md +46 -0
- package/docs/component-contract.md +31 -32
- package/docs/component-selection.md +228 -164
- package/docs/design/templates/button/emphasis-dark.svg +1 -0
- package/docs/design/templates/button/emphasis.svg +1 -0
- package/docs/design/templates/button/icons-dark.svg +1 -0
- package/docs/design/templates/button/icons.svg +1 -0
- package/docs/design/templates/button/pill-dark.svg +1 -0
- package/docs/design/templates/button/pill.svg +1 -0
- package/docs/design/templates/button/states-dark.svg +1 -0
- package/docs/design/templates/button/states.svg +1 -0
- package/docs/design/templates/button/toggles-dark.svg +1 -0
- package/docs/design/templates/button/toggles.svg +1 -0
- package/docs/design/templates/button/tones-dark.svg +1 -0
- package/docs/design/templates/button/tones.svg +1 -0
- package/docs/design/templates/button-dark.svg +1 -0
- package/docs/design/templates/button.svg +1 -0
- package/docs/design/templates/checkbox/compact-dark.svg +1 -0
- package/docs/design/templates/checkbox/compact.svg +1 -0
- package/docs/design/templates/checkbox/error-dark.svg +1 -0
- package/docs/design/templates/checkbox/error.svg +1 -0
- package/docs/design/templates/checkbox/mixed-dark.svg +1 -0
- package/docs/design/templates/checkbox/mixed.svg +1 -0
- package/docs/design/templates/checkbox/states-dark.svg +1 -0
- package/docs/design/templates/checkbox/states.svg +1 -0
- package/docs/design/templates/checkbox-dark.svg +1 -0
- package/docs/design/templates/checkbox.svg +1 -0
- package/docs/design/templates/choice-chips/color-palette-dark.svg +1 -1
- package/docs/design/templates/choice-chips/color-palette.svg +1 -1
- package/docs/design/templates/choice-chips/compact-icons-dark.svg +1 -1
- package/docs/design/templates/choice-chips/compact-icons.svg +1 -1
- package/docs/design/templates/choice-chips/multiple-dark.svg +1 -1
- package/docs/design/templates/choice-chips/multiple.svg +1 -1
- package/docs/design/templates/choice-chips-dark.svg +1 -1
- package/docs/design/templates/choice-chips.svg +1 -1
- package/docs/design/templates/color-field/compact-dark.svg +1 -0
- package/docs/design/templates/color-field/compact.svg +1 -0
- package/docs/design/templates/color-field/error-dark.svg +1 -0
- package/docs/design/templates/color-field/error.svg +1 -0
- package/docs/design/templates/color-field/hint-dark.svg +1 -0
- package/docs/design/templates/color-field/hint.svg +1 -0
- package/docs/design/templates/color-field-dark.svg +1 -0
- package/docs/design/templates/color-field.svg +1 -0
- package/docs/design/templates/color-picker/gray-dark.svg +1 -0
- package/docs/design/templates/color-picker/gray.svg +1 -0
- package/docs/design/templates/color-picker/rgb-dark.svg +1 -0
- package/docs/design/templates/color-picker/rgb.svg +1 -0
- package/docs/design/templates/color-picker-dark.svg +1 -0
- package/docs/design/templates/color-picker.svg +1 -0
- package/docs/design/templates/color-swatches/palette-dark.svg +1 -0
- package/docs/design/templates/color-swatches/palette.svg +1 -0
- package/docs/design/templates/color-swatches-dark.svg +1 -0
- package/docs/design/templates/color-swatches.svg +1 -0
- package/docs/design/templates/disclosure/framed-sunken-dark.svg +1 -0
- package/docs/design/templates/disclosure/framed-sunken.svg +1 -0
- package/docs/design/templates/disclosure/plain-dark.svg +1 -0
- package/docs/design/templates/disclosure/plain.svg +1 -0
- package/docs/design/templates/disclosure-dark.svg +1 -0
- package/docs/design/templates/disclosure.svg +1 -0
- package/docs/design/templates/list-header/size-l-dark.svg +1 -1
- package/docs/design/templates/list-header/size-l.svg +1 -1
- package/docs/design/templates/list-header/size-m-dark.svg +1 -1
- package/docs/design/templates/list-header/size-m.svg +1 -1
- package/docs/design/templates/list-header/size-xl-dark.svg +1 -1
- package/docs/design/templates/list-header/size-xl.svg +1 -1
- package/docs/design/templates/list-header-dark.svg +1 -1
- package/docs/design/templates/list-header.svg +1 -1
- package/docs/design/templates/message-composer/chat-dark.svg +1 -0
- package/docs/design/templates/message-composer/chat.svg +1 -0
- package/docs/design/templates/{composer → message-composer}/prompt-dark.svg +1 -1
- package/docs/design/templates/{composer → message-composer}/prompt.svg +1 -1
- package/docs/design/templates/message-composer-dark.svg +1 -0
- package/docs/design/templates/message-composer.svg +1 -0
- package/docs/design/templates/progress-bar/determinate-dark.svg +1 -0
- package/docs/design/templates/progress-bar/determinate.svg +1 -0
- package/docs/design/templates/progress-bar/tones-dark.svg +1 -0
- package/docs/design/templates/progress-bar/tones.svg +1 -0
- package/docs/design/templates/progress-bar-dark.svg +1 -0
- package/docs/design/templates/progress-bar.svg +1 -0
- package/docs/design/templates/progress-ring/sizes-dark.svg +1 -0
- package/docs/design/templates/progress-ring/sizes.svg +1 -0
- package/docs/design/templates/progress-ring/tones-dark.svg +1 -0
- package/docs/design/templates/progress-ring/tones.svg +1 -0
- package/docs/design/templates/progress-ring-dark.svg +1 -0
- package/docs/design/templates/progress-ring.svg +1 -0
- package/docs/design/templates/radio-group/horizontal-dark.svg +1 -0
- package/docs/design/templates/radio-group/horizontal.svg +1 -0
- package/docs/design/templates/radio-group/vertical-dark.svg +1 -0
- package/docs/design/templates/radio-group/vertical.svg +1 -0
- package/docs/design/templates/radio-group-dark.svg +1 -0
- package/docs/design/templates/radio-group.svg +1 -0
- package/docs/design/templates/segmented-control/equal-dark.svg +1 -1
- package/docs/design/templates/segmented-control/equal.svg +1 -1
- package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -1
- package/docs/design/templates/segmented-control/pill-small.svg +1 -1
- package/docs/design/templates/segmented-control-dark.svg +1 -1
- package/docs/design/templates/segmented-control.svg +1 -1
- package/docs/design/templates/select/compact-dark.svg +1 -0
- package/docs/design/templates/select/compact.svg +1 -0
- package/docs/design/templates/select/form-dark.svg +1 -0
- package/docs/design/templates/select/form.svg +1 -0
- package/docs/design/templates/select/icons-dark.svg +1 -0
- package/docs/design/templates/select/icons.svg +1 -0
- package/docs/design/templates/select-dark.svg +1 -0
- package/docs/design/templates/select.svg +1 -0
- package/docs/design/templates/slider/compact-dark.svg +1 -0
- package/docs/design/templates/slider/compact.svg +1 -0
- package/docs/design/templates/slider/readout-dark.svg +1 -0
- package/docs/design/templates/slider/readout.svg +1 -0
- package/docs/design/templates/slider/ticks-dark.svg +1 -0
- package/docs/design/templates/slider/ticks.svg +1 -0
- package/docs/design/templates/slider-dark.svg +1 -0
- package/docs/design/templates/slider.svg +1 -0
- package/docs/design/templates/switch/compact-dark.svg +1 -0
- package/docs/design/templates/switch/compact.svg +1 -0
- package/docs/design/templates/switch/states-dark.svg +1 -0
- package/docs/design/templates/switch/states.svg +1 -0
- package/docs/design/templates/switch-dark.svg +1 -0
- package/docs/design/templates/switch.svg +1 -0
- package/docs/design/templates/tag-field/error-dark.svg +1 -0
- package/docs/design/templates/tag-field/error.svg +1 -0
- package/docs/design/templates/tag-field/tags-dark.svg +1 -0
- package/docs/design/templates/tag-field/tags.svg +1 -0
- package/docs/design/templates/tag-field-dark.svg +1 -0
- package/docs/design/templates/tag-field.svg +1 -0
- package/docs/design/templates/text-field/compact-dark.svg +1 -0
- package/docs/design/templates/text-field/compact.svg +1 -0
- package/docs/design/templates/text-field/error-dark.svg +1 -0
- package/docs/design/templates/text-field/error.svg +1 -0
- package/docs/design/templates/text-field/labeled-dark.svg +1 -0
- package/docs/design/templates/text-field/labeled.svg +1 -0
- package/docs/design/templates/text-field/multiline-dark.svg +1 -0
- package/docs/design/templates/text-field/multiline.svg +1 -0
- package/docs/design/templates/text-field/search-dark.svg +1 -0
- package/docs/design/templates/text-field/search.svg +1 -0
- package/docs/design/templates/text-field-dark.svg +1 -0
- package/docs/design/templates/text-field.svg +1 -0
- package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
- package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
- package/docs/design/templates/toolbar/width-visibility-320-dark.svg +1 -1
- package/docs/design/templates/toolbar/width-visibility-320.svg +1 -1
- package/docs/design/templates/toolbar/width-visibility-600-dark.svg +1 -1
- package/docs/design/templates/toolbar/width-visibility-600.svg +1 -1
- package/docs/design/templates/toolbar-dark.svg +1 -1
- package/docs/design/templates/toolbar.svg +1 -1
- package/docs/design/templates/value-table/details-dark.svg +1 -1
- package/docs/design/templates/value-table/details.svg +1 -1
- package/docs/design/templates/value-table-dark.svg +1 -1
- package/docs/design/templates/value-table.svg +1 -1
- package/docs/design/templates.md +19 -6
- package/docs/design-philosophy.md +6 -6
- package/docs/disclosure.md +63 -0
- package/docs/examples/component-catalog-extension.json +2 -2
- package/docs/layout.md +16 -7
- package/docs/list-detail-dialog.md +96 -0
- package/docs/manual-test-plan.md +1 -1
- package/docs/{composer.md → message-composer.md} +41 -28
- package/docs/overlay-toast.md +62 -0
- package/docs/overlay-tooltip.md +53 -0
- package/docs/progress.md +62 -0
- package/docs/recipes.md +15 -21
- package/docs/slider.md +64 -0
- package/docs/surface-scaffold.md +155 -88
- package/docs/tag-field.md +66 -0
- package/docs/type-contracts.md +8 -8
- package/docs/ui-analyzer.md +1 -1
- package/docs/ui-doctor.md +11 -6
- package/docs/ux-demo.md +6 -6
- package/docs/webawesome-migration.md +80 -0
- package/docs/webawesome-theme.md +57 -35
- package/docs/workbench-dialog.md +85 -0
- package/docs/workbench.md +10 -6
- package/docs/writing.md +10 -8
- package/doctor/copy-capitalization-checks.mjs +18 -9
- package/llms.txt +127 -57
- package/package.json +156 -39
- package/ux-demo/recipes/composer-form.tsx +18 -19
- package/ux-demo/recipes/list-detail-dialog.tsx +55 -162
- package/ux-demo/recipes/mount-recipe.ts +7 -6
- package/ux-demo/recipes/types.ts +1 -0
- package/ai/components/icon-button.md +0 -82
- package/ai/components/icon-toggle-button.md +0 -80
- package/dist/app-tab.js +0 -6
- package/dist/app-tab.js.map +0 -1
- package/dist/browser/app-tab.js +0 -4
- package/dist/browser/composer.js +0 -4
- package/dist/browser/icon-toggle-button.js +0 -4
- package/dist/browser/token-search-field.js +0 -4
- package/dist/chunk-2MSPNRWJ.js.map +0 -1
- package/dist/chunk-7IUEBNCO.js.map +0 -1
- package/dist/chunk-AV7CPAPT.js +0 -82
- package/dist/chunk-AV7CPAPT.js.map +0 -1
- package/dist/chunk-BYFTHA4I.js.map +0 -1
- package/dist/chunk-CA7LNX3J.js +0 -44
- package/dist/chunk-CA7LNX3J.js.map +0 -1
- package/dist/chunk-CR5U5FV6.js.map +0 -1
- package/dist/chunk-ELCMSQNJ.js.map +0 -1
- package/dist/chunk-ETCWQLJV.js.map +0 -1
- package/dist/chunk-FDZNYD4C.js.map +0 -1
- package/dist/chunk-FR76ZJQC.js +0 -90
- package/dist/chunk-FR76ZJQC.js.map +0 -1
- package/dist/chunk-HQZYKJ56.js.map +0 -1
- package/dist/chunk-HZPHCSCS.js.map +0 -1
- package/dist/chunk-JOR5LGYE.js.map +0 -1
- package/dist/chunk-K4AT3V3J.js.map +0 -1
- package/dist/chunk-LL45C7BB.js.map +0 -1
- package/dist/chunk-LOSRXZAH.js.map +0 -1
- package/dist/chunk-LPHU44S3.js.map +0 -1
- package/dist/chunk-M5KZ26VV.js.map +0 -1
- package/dist/chunk-MXNIVEVL.js.map +0 -1
- package/dist/chunk-NS762OVM.js +0 -222
- package/dist/chunk-NS762OVM.js.map +0 -1
- package/dist/chunk-QTSMHH5J.js.map +0 -1
- package/dist/chunk-T5BJBMW7.js.map +0 -1
- package/dist/chunk-TDMHA7C3.js.map +0 -1
- package/dist/chunk-TGLUDQP2.js.map +0 -1
- package/dist/chunk-U6QYTMDB.js.map +0 -1
- package/dist/chunk-VFYO3UGZ.js +0 -45
- package/dist/chunk-VFYO3UGZ.js.map +0 -1
- package/dist/chunk-W4IK7AWD.js.map +0 -1
- package/dist/chunk-YBWQR2OK.js.map +0 -1
- package/dist/chunk-ZJC5ND3M.js.map +0 -1
- package/dist/composer.js +0 -5
- package/dist/composer.js.map +0 -1
- package/dist/icon-button.d.ts +0 -46
- package/dist/icon-button.js +0 -6
- package/dist/icon-button.js.map +0 -1
- package/dist/icon-toggle-button.d.ts +0 -42
- package/dist/icon-toggle-button.js +0 -6
- package/dist/icon-toggle-button.js.map +0 -1
- package/dist/styles/components/actions/icon-button/icon-button.css +0 -22
- package/dist/styles/components/actions/icon-toggle-button/icon-toggle-button.css +0 -83
- package/dist/styles/icon-button.css +0 -22
- package/dist/styles/icon-toggle-button.css +0 -83
- package/dist/token-search-field.d.ts +0 -3
- package/dist/token-search-field.js +0 -5
- package/dist/token-search-field.js.map +0 -1
- package/dist/token-search-model.d.ts +0 -3
- package/dist/token-search-model.js +0 -3
- package/dist/token-search-model.js.map +0 -1
- package/dist/wire-composers.js +0 -3
- package/dist/wire-token-search-fields.js +0 -6
- package/docs/design/templates/composer/chat-dark.svg +0 -1
- package/docs/design/templates/composer/chat.svg +0 -1
- package/docs/design/templates/composer-dark.svg +0 -1
- package/docs/design/templates/composer.svg +0 -1
- /package/docs/design/templates/{token-search-field → search-field}/empty-dark.svg +0 -0
- /package/docs/design/templates/{token-search-field → search-field}/empty.svg +0 -0
- /package/docs/design/templates/{token-search-field → search-field}/tokens-dark.svg +0 -0
- /package/docs/design/templates/{token-search-field → search-field}/tokens.svg +0 -0
- /package/docs/design/templates/{token-search-field-dark.svg → search-field-dark.svg} +0 -0
- /package/docs/design/templates/{token-search-field.svg → search-field.svg} +0 -0
package/ai/skill.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kerf-ui
|
|
3
3
|
description: Build interfaces with kerfjs and the @kerfjs/ui production component package. Use whenever code imports @kerfjs/ui or a task asks for Kerf UI components.
|
|
4
|
-
kerf-ui-skill-version: 1.
|
|
4
|
+
kerf-ui-skill-version: 1.63.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Building with @kerfjs/ui
|
|
@@ -131,7 +131,7 @@ Pane override to imitate the Kerf UI UX demo.
|
|
|
131
131
|
- **Compose, then let the child adapt.** When a component needs to look
|
|
132
132
|
different inside another one, the child styles itself in that context, in
|
|
133
133
|
its own stylesheet (`.kui-toolbar-control-group[data-size="compact"] >
|
|
134
|
-
|
|
134
|
+
.kui-popup-menu { … }` lives with PopupMenu). A parent may key its
|
|
135
135
|
own styles on a child's state (`:has(> .kui-select[open])`) and name the
|
|
136
136
|
child by class, never by bare `wa-*` tag. Values a parent provides to its
|
|
137
137
|
children are custom properties named after the parent.
|
|
@@ -248,7 +248,7 @@ failure as `KUI-L090`.
|
|
|
248
248
|
|
|
249
249
|
Choose from the need, not from visual resemblance:
|
|
250
250
|
|
|
251
|
-
1. Search the consumer extension first, then the Kerf catalog
|
|
251
|
+
1. Search the consumer extension first, then the Kerf catalog. Prefer a Kerf UI component, then a native HTML element, using each naturally for what it is good at. Web Awesome is an optional integration: use its remaining supported elements only in an application that has adopted it, never one a Kerf component replaces.
|
|
252
252
|
2. Reuse a primitive when purpose, anatomy, state, and interaction match.
|
|
253
253
|
3. Compose primitives for recurring layout through public props and tokens. Never select one component from another component's stylesheet.
|
|
254
254
|
4. Add a thin application adapter for copy, domain mapping, actions, routing, persistence, permissions, and transport.
|
|
@@ -292,23 +292,23 @@ reports lowercase words in known title-style labels as `KUI-D040`. See
|
|
|
292
292
|
|
|
293
293
|
Quick routing:
|
|
294
294
|
|
|
295
|
-
| Need | Choose | Nearest alternatives / boundary
|
|
296
|
-
| ------------------------------------- | ---------------------------------------------------------- |
|
|
297
|
-
| 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.
|
|
298
|
-
| Horizontal flex layout | `Row` | Use `hAlign`/`vAlign`, a typed `gap`, and opt-in `wrap`; use `List` when the main axis is vertical.
|
|
299
|
-
| Equal-width columns | `Grid` | Use fixed `columns` or typed `minColumnWidth` for automatic container-width collapse, plus typed `gap`/`flex`. Use CSS grid for asymmetric/intrinsic tracks and `ResizableRegion` for adjustable boundaries.
|
|
300
|
-
| 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. A trailing "…" overflow menu is `trailingMenu` (a real `PopupMenu`), not a trailing button.
|
|
301
|
-
| Page, panel, or dialog heading | `Toolbar` + `ToolbarText` | Use a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and short supporting copy as the `ToolbarText` `description` (quiet, one line, aligned under the title, with `descriptionId` for `aria-describedby`; longer copy stays app-owned content below).
|
|
302
|
-
| Exclusive choice | `TabBar`, `SegmentedControl`, or `Select` | Tabs switch tabpanels; segments expose a few choices; `ChoiceChips` keeps a larger visible set as wrapping pills (`multiple` for any number); Select handles a longer value list.
|
|
303
|
-
| Status or absent content | `StateBanner` or `EmptyState` | `StateBanner` accepts an optional terse, tone-tinted `badge` beside its title for a count or compact status; do not rely on color alone.
|
|
304
|
-
| Compact metadata | `Badge` | Configure status, count, category, or metadata presentation with tone, appearance, shape, and size props. For new content without a count use `size="dot"` (no children) with a `label` or `ariaHidden`. Cap long app-supplied text with `maxWidth` (a `CssLength`); it truncates with an ellipsis and the full string becomes the `title`. Do not build badges with application CSS or use them as interactive controls.
|
|
305
|
-
| Hero image atop a scrolling view | `BackdropImage` | Lags the scroll (`parallax`), blurs progressively (`blur`, an opacity cross-fade to a once-blurred copy), dissolves into a typed `fadeTo` color that also paints the content below, and carries `overlay` content; Kerf owns the compositor-only scroll timeline and the reduced-motion, static-effects, and reduced-transparency fallbacks. Never hand-roll scroll listeners, `background-attachment: fixed`, or mask gradients.
|
|
306
|
-
| Loading placeholder | a component's `placeholder` prop, or `Skeleton` | Set `placeholder` on a value-bearing component (`Select`, `ListHeader`, `ListItem`, `ValueTableRow`, `SegmentedControl`, `StateBanner`, `
|
|
307
|
-
| Adjustable or asymmetric columns | `ResizableRegion` or CSS grid | Use `ResizableRegion` only for a user-operable controlled separator, CSS grid for asymmetric/intrinsic tracks, and `Grid` for static equal-width tracks.
|
|
308
|
-
| Structured or ordinary search | `
|
|
309
|
-
| Command menu | `PopupMenu` | Put it in a `single` `ToolbarControlGroup` with `nestedDropdown`; register `@kerfjs/ui/popup-menu/register` once. It draws the same popup as `Select` (caret, rows, check column, group titles); do not restyle either to match the other. Use `Select` when the user chooses a persistent value.
|
|
310
|
-
| Key/value facts | `ValueTable` with `ValueTableRow` | Use rows with `icon` only when the optional 24px leading visual adds context.
|
|
311
|
-
| Command search | Application-local semantics using shared layout vocabulary | No runtime component or production recipe is exported; the app owns behavior, policy, and copy.
|
|
295
|
+
| Need | Choose | Nearest alternatives / boundary |
|
|
296
|
+
| ------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
297
|
+
| 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. |
|
|
298
|
+
| Horizontal flex layout | `Row` | Use `hAlign`/`vAlign`, a typed `gap`, and opt-in `wrap`; use `List` when the main axis is vertical. |
|
|
299
|
+
| Equal-width columns | `Grid` | Use fixed `columns` or typed `minColumnWidth` for automatic container-width collapse, plus typed `gap`/`flex`. Use CSS grid for asymmetric/intrinsic tracks and `ResizableRegion` for adjustable boundaries. |
|
|
300
|
+
| 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. A trailing "…" overflow menu is `trailingMenu` (a real `PopupMenu`), not a trailing button. |
|
|
301
|
+
| Page, panel, or dialog heading | `Toolbar` + `ToolbarText` | Use a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and short supporting copy as the `ToolbarText` `description` (quiet, one line, aligned under the title, with `descriptionId` for `aria-describedby`; longer copy stays app-owned content below). |
|
|
302
|
+
| Exclusive choice | `TabBar`, `SegmentedControl`, or `Select` | Tabs switch tabpanels; segments expose a few choices; `ChoiceChips` keeps a larger visible set as wrapping pills (`multiple` for any number); Select handles a longer value list. |
|
|
303
|
+
| Status or absent content | `StateBanner` or `EmptyState` | `StateBanner` accepts an optional terse, tone-tinted `badge` beside its title for a count or compact status; do not rely on color alone. toasts (`toast()` from `kerfjs/overlay`) are transient and never the only record of important state; import `@kerfjs/ui/overlay-toast.css` once to style them (pass `exitClass: 'kui-toast-exit'`, `exitDuration: 200`, `variant` for a tone, or a `StateBanner` as the only content) instead of `wa-toast` or hand-written toast CSS. A hover and focus explanation on any element is `tooltip()` from `kerfjs/overlay` with `@kerfjs/ui/overlay-tooltip.css` imported (`exitClass: 'kui-tooltip-exit'`, `exitDuration: 100`), never `wa-tooltip`; icon-only Kerf controls already get help tags. Show-and-hide content is `Disclosure` (`@kerfjs/ui/disclosure`, a native details element on content-item geometry; Disclosures sharing a `name` form an accordion; close one from code with `element.open = false`), never `wa-details` or `wa-accordion`. Progress is `ProgressBar` (`@kerfjs/ui/progress-bar`, a native progress element with a `label` or `ariaLabel`, `showValue`/`valueText`, and an indeterminate sweep without `value`) or, where a bar does not fit, `ProgressRing` (`@kerfjs/ui/progress-ring`, `small`/`medium`/`large`), never `wa-progress-bar` or `wa-progress-ring`. A value on a range is `Slider` (`@kerfjs/ui/slider`, a native range input with `label`, `showValue`/`unit`, `ticks`, and `hint`; call `wireSliders(root)` from `@kerfjs/ui/wire-sliders` once so the fill and readout follow a drag), never `wa-slider`. A list of short values submitted as form values is `TagField` (`@kerfjs/ui/tag-field`, SearchField's token editor as a form field with a hidden input per tag; call `wireTagFields(root, { onChange })` from `@kerfjs/ui/wire-tag-fields` and re-render with the reported tags; `max`, `normalize`, and `onReject` for validation), never `wa-tag-input`. One color from a small fixed palette is `ColorSwatches` (`@kerfjs/ui/color-swatches`, a native radio group of named round swatches with a hue-independent ring and an optional `'transparent'` no-color swatch). Any color is `ColorPicker` (`@kerfjs/ui/color-picker`, an inline panel with an area, hue and opacity sliders, RGB/HSL/HSB/Grayscale fields, hex, presets, and samples; call `wireColorPickers(root, { onChange })` from `@kerfjs/ui/wire-color-pickers` and re-render with the reported `value` and `mode`). A color in a form or inspector is `ColorField` (`@kerfjs/ui/color-field`, a swatch-and-hex trigger that opens a ColorPicker in an AnchoredSurface; call `wireColorFields(root, { onChange, pickers })` from `@kerfjs/ui/wire-color-fields` and re-render), never `wa-color-picker`. |
|
|
304
|
+
| Compact metadata | `Badge` | Configure status, count, category, or metadata presentation with tone, appearance, shape, and size props. For new content without a count use `size="dot"` (no children) with a `label` or `ariaHidden`. Cap long app-supplied text with `maxWidth` (a `CssLength`); it truncates with an ellipsis and the full string becomes the `title`. Do not build badges with application CSS or use them as interactive controls. |
|
|
305
|
+
| Hero image atop a scrolling view | `BackdropImage` | Lags the scroll (`parallax`), blurs progressively (`blur`, an opacity cross-fade to a once-blurred copy), dissolves into a typed `fadeTo` color that also paints the content below, and carries `overlay` content; Kerf owns the compositor-only scroll timeline and the reduced-motion, static-effects, and reduced-transparency fallbacks. Never hand-roll scroll listeners, `background-attachment: fixed`, or mask gradients. |
|
|
306
|
+
| Loading placeholder | a component's `placeholder` prop, or `Skeleton` | Set `placeholder` on a value-bearing component (`Select`, `ListHeader`, `ListItem`, `ValueTableRow`, `SegmentedControl`, `StateBanner`, `TabBarItem`, `ToolbarText`, `ListActionRow`) to render its real chrome with value slots as subtle **unanimated** skeletons and interactivity disabled; only unknown values change, so a placeholder is never dimmed or styled as disabled (a control whose own `disabled` / `trailingActionDisabled` / choice `disabled` / `actionDisabled` prop is set keeps its live disabled tone via the reserved `data-kui-disabled` marker) — do not hand-rebuild an empty state. Component-drawn chrome stays live too: a `ListItem` or `ListActionRow` placeholder keeps its `busy` spinner while its `status` text (and a `ListItem`'s `trailing` content, always a value) becomes a skeleton. `Skeleton` is the standalone block for a custom slot. A known `TabBarItem` that is still opening takes `pending` instead: it keeps its name (quiet, and as its accessible name) and the same geometry, shows your `trailing` spinner, and stays selectable (close and drag disabled) so your panel can show placeholders until it loads. Use `LoadingSpinner` for known busy activity, not a skeleton; never animate a skeleton; prefer it over `wa-skeleton`. |
|
|
307
|
+
| Adjustable or asymmetric columns | `ResizableRegion` or CSS grid | Use `ResizableRegion` only for a user-operable controlled separator, CSS grid for asymmetric/intrinsic tracks, and `Grid` for static equal-width tracks. |
|
|
308
|
+
| Structured or ordinary search | `SearchField` or an input | Use token search only when text and ordered filter chips form one editor. |
|
|
309
|
+
| Command menu | `PopupMenu` | Put it in a `single` `ToolbarControlGroup` with `nestedDropdown`; register `@kerfjs/ui/popup-menu/register` once. It draws the same popup as `Select` (caret, rows, check column, group titles); do not restyle either to match the other. Use `Select` when the user chooses a persistent value. |
|
|
310
|
+
| Key/value facts | `ValueTable` with `ValueTableRow` | Use rows with `icon` only when the optional 24px leading visual adds context. |
|
|
311
|
+
| Command search | Application-local semantics using shared layout vocabulary | No runtime component or production recipe is exported; the app owns behavior, policy, and copy. |
|
|
312
312
|
|
|
313
313
|
For equal-width responsive forms, set `Grid minColumnWidth={px(376)}` instead of
|
|
314
314
|
`columns`. Grid fits tracks from its own container width and collapses to one
|
|
@@ -316,8 +316,11 @@ when a second minimum-width track plus the gap no longer fits. Constrain the
|
|
|
316
316
|
outer Grid width when the form should have a maximum column count.
|
|
317
317
|
For selectable document tiles, set `Grid selectionMode="multiple"` and
|
|
318
318
|
`ariaLabel`, render `ContentItem selectionMode="multiple"` children, and wire
|
|
319
|
-
`wireContentItems` for arrows, Home/End, and
|
|
320
|
-
selection set and each tile's nested controls
|
|
319
|
+
`wireContentItems` for arrows, Home/End, card activation, and a single roving Tab stop; wrap non-selectable rows between them (activity groups, dividers) in `ContentItem gridRow` instead of hand-written grid roles. The app owns the
|
|
320
|
+
selection set and each tile's nested controls; in its click handler, pass
|
|
321
|
+
`selectionGesture(event)` and the visible ids to `applySelection` from
|
|
322
|
+
`@kerfjs/ui/selection` so a click replaces, Cmd/Ctrl (`isApplePlatform`
|
|
323
|
+
normalizes it) toggles, and Shift extends from the returned anchor.
|
|
321
324
|
`textInsets` adds the standard 17px physical-side content inset; `controlInsets`
|
|
322
325
|
adds 8px. Text insets win on any overlapping side, matching Row and List.
|
|
323
326
|
|
|
@@ -360,46 +363,72 @@ Hard rules:
|
|
|
360
363
|
|
|
361
364
|
1. Import visual components from explicit JavaScript subpaths. In CSS-aware browser builds each subpath brings in its own reachable CSS, including UI subcomponents, while unrelated CSS remains out. The root barrel and `@kerfjs/ui/unstyled` are CSS-free; pair the root barrel with `styles.css` only when the complete layer is intentional. Manual CSS subpaths remain available for custom pipelines. When a public subpath exposes several visual components, implement it as a folder with one component per file and one owned stylesheet per visual component; compatibility entrypoints only re-export the component modules or import their owner stylesheets.
|
|
362
365
|
2. Components return Kerf `SafeHtml`. Never pass DOM nodes as children or use inline JSX event handlers.
|
|
363
|
-
3. Keep state, product copy, persistence, and domain mappings in the application. Do not add product-specific actions or fields to a generic component. Put ListItem/ListActionRow/ListHeader/
|
|
366
|
+
3. Keep state, product copy, persistence, and domain mappings in the application. Do not add product-specific actions or fields to a generic component. Put ListItem/ListActionRow/ListHeader/TabBarItem domain `data-*` metadata in `rootAttributes`; use ListActionRow `trailingActionAttributes` and ListHeader `triggerAttributes` only for domain `data-*` or native popover target/action and `aria-controls`/`aria-haspopup`. These slots do not replace component-owned action, item/tab identity, selection, disclosure, naming, disabled, icon, or role semantics.
|
|
364
367
|
4. Wire `data-action` hooks from one stable root with `delegate()` or `delegateActions()` and retain disposers.
|
|
365
368
|
5. Configure components through their typed props; components own their own styles (see "Components own their styles; configure, never override" above). Use the opinionated `--kui-color-*` semantic ramps for theming, not to reshape a component. Use `pop` only for attractive non-status emphasis such as featured, novel, or celebratory content; never substitute it for info, success, warning, or danger. Set a theme token only at the narrowest useful scope, and never where an equivalent prop exists. `publicClasses` exist for diagnostics, tooling, tests, and package-owned composition, not application styling. Never target a Kerf or Web Awesome component from application CSS; add missing configuration to the owner.
|
|
366
|
-
6. `Select` is pure until the app explicitly imports `@kerfjs/ui/select/register`; once registered, an icon-only `Select` (and, via `@kerfjs/ui/popup-menu/register`, an icon-only `PopupMenu`) shows an `aria-hidden` help tag repeating its accessible name (and a Select's current choice) on hover and keyboard focus, hidden while its popup is open — never add a `title` or your own tooltip to these triggers. The same installer tags an icon-only `<button>` or link in a `ToolbarControlGroup` named by `aria-label` (no visible text, no `title`); a toolbar with such buttons but no registered Select or PopupMenu imports `@kerfjs/ui/help-tags/register` once. Name those buttons with `aria-label`, not `title`: a `title` keeps the browser tooltip and skips the tag. Do not import Web Awesome's full registration bundle. When an app writes direct `wa-*` JSX, add `import type {} from '@kerfjs/ui/webawesome'` for the catalog-supported intrinsic-element declarations, import the CSS-only `@kerfjs/ui/webawesome.css` theme once, and keep importing individual Web Awesome component modules so their JavaScript remains tree-shakeable. The type boundary emits no code and registers nothing. Pass icon-bearing `choices` and `renderSelected` content normally: `Select` preserves its
|
|
369
|
+
6. `Select` is pure until the app explicitly imports `@kerfjs/ui/select/register`; once registered, an icon-only `Select` (and, via `@kerfjs/ui/popup-menu/register`, an icon-only `PopupMenu`) shows an `aria-hidden` help tag repeating its accessible name (and a Select's current choice) on hover and keyboard focus, hidden while its popup is open — never add a `title` or your own tooltip to these triggers. The same installer tags an icon-only `<button>` or link in a `ToolbarControlGroup` named by `aria-label` (no visible text, no `title`); a toolbar with such buttons but no registered Select or PopupMenu imports `@kerfjs/ui/help-tags/register` once. Name those buttons with `aria-label`, not `title`: a `title` keeps the browser tooltip and skips the tag. Do not import Web Awesome's full registration bundle. When an app writes direct `wa-*` JSX, add `import type {} from '@kerfjs/ui/webawesome'` for the catalog-supported intrinsic-element declarations, import the CSS-only `@kerfjs/ui/webawesome.css` theme once, and keep importing individual Web Awesome component modules so their JavaScript remains tree-shakeable. The type boundary emits no code and registers nothing. Pass icon-bearing `choices` and `renderSelected` content normally: `Select` preserves its option icons across Kerf rerenders and keys selected content by the controlled value, so app wrappers must not add competing morph-control attributes. Give it a visible `label` or an `ariaLabel` for a visually hidden name; the nonempty visible label takes precedence and the package names its native trigger, including with `renderSelected`, without adding label geometry. Use `hint` for persistent supporting text below the control and `placeholderText` only for the empty value inside the closed control; loading placeholders retain the hint. For any number of choices set `multiple` with an array `value`: the popup stays open while choices toggle and closes on an outside press or Escape, the closed control summarizes the chosen labels, and `change` reports the value array; do not add app code to keep it open or to render tags. For a toolbar filter menu, add `selectedPresentation="icon-only"` plus a fixed `triggerIcon` (such as a funnel `LucideIcon`): the trigger shows that icon, a count badge while any choice is chosen, and ends its accessible name with the chosen labels ("Filter by label: Bug, Docs"); do not render your own count badge or rewrite the name. When testing name or description, target the element with the `combobox` role (the native trigger) rather than the `kui-select` root; the hint is already its `aria-description`, so do not add another.
|
|
367
370
|
7. Decorative icons are hidden; controls are named; focus is visible; state never relies on color alone; reduced motion and increased contrast remain usable. When the app detects a low-power device, it sets `data-kui-effects="static"` on the root element to turn backdrop materials opaque and stop scroll-driven animations; `@media (update: slow)` does the same automatically. `DisclosureArrow` defaults to an 18px root-scaled visual; size it with its typed `size` prop (`size={remify(24)}`); it never becomes the interaction or accessible-name owner. Put it in an owning native control with a stable accessible name and controlled `aria-expanded`. `ListHeader` toggle mode supplies it when `actionIcon` is omitted, but the app must reveal matching content; a custom icon replaces it. Author replacement icon content facing right before its configured direction transform is applied. Direction changes take the shortest rotation path, with counterclockwise chosen for a 180-degree closed-to-open tie. Kerf `Select` separately keeps its intrinsic Web Awesome expand glyph at `--kui-disclosure-icon-scale: .5`; an icon-only toolbar Select may set `caret={false}` for a round trigger while preserving its help tag and keyboard listbox, and a custom `renderSelected` icon stays centered.
|
|
368
371
|
8. `ResizableRegion` uses `wireResizableRegions()` for Arrow, Shift+Arrow, Home/End, and pointer behavior. The app owns size persistence. `handleIcon` replaces decorative dormant glyph content only. Set `responsiveFillAt="narrow"` or `"compact"` when a responsive composition exposes the pane as its only inline track; the component fills that track and hides its dormant separator. For compact overlays, set `presentation="overlay"` and the overlay maximum variables. A lone child (normally a `Pane`) fills the region and owns scrolling; several children stack at natural height. Do not add descendant width/height overrides.
|
|
369
372
|
9. Use controlled `SegmentedControl` for a small exclusive choice set. Select `appearance="toolbar"` when nesting it inside `ToolbarControlGroup`; use rounded or pill shapes for standalone contexts. The package derives inset hover/selection radii from the owning outer shape through `--kui-layout-highlight-inset`; do not restyle an inner highlight to an independent radius. Handle its action, update `value`, keep meaningful choice labels, and preserve every enabled native button in sequential Tab order.
|
|
370
|
-
10. Compose `
|
|
371
|
-
11. Use token-controlled `
|
|
373
|
+
10. Compose `TabBarItem` inside controlled `TabBar`; call `wireTabBars()` once and retain its disposer. It owns same-bar drag mechanics, including proximity-based horizontal edge autoscroll, while the app applies `onReorder` and owns order, selection, panels, close policy, routing, and persistence. For a tab-local action followed by a separately pinned workspace action, set `trailingPlacement="adjacent"`, put the local action in `trailing`, and the workspace action in `end`; each action zone accepts one `ToolbarControlGroup` or a standalone `wa-button` when the action needs its own Web Awesome chrome. TabBar owns the shrinking/scrolling tabs and fixed action geometry, so do not add descendant flex overrides. Arrow/Home/End select on move by default and restore focus by bar/tab identity when controlled activation replaces the strip; pass `activation: 'manual'` (or `TabBar activation="manual"` per strip) so arrow keys move focus only and the user selects with Enter/Space/click — use it when selecting a tab is a heavy action. Use runtime-filtered `rootAttributes` for domain metadata and keep an optional `closeIcon` decorative and noninteractive.
|
|
374
|
+
11. Use token-controlled `SearchField` when free text and removable structured filters share one editor. Editable text stays DOM-owned between token changes; to replace the text programmatically (reseeding a persistent dialog's field on reopen, applying a saved search) change the field's `revision` along with `query` for an unmanaged field, or call `model.replace({ query, tokens })` for a model-managed field; never re-key a wrapper. The leading icon, first text line, clear action, and trailing slot share one fixed row when content wraps. For automatic parsing, chips, suggestions, and evaluation, use createSearchFieldModel with grammar rules, pass it as SearchField.model, and register it in wireSearchFields models; a rule's `suggest(input, state)` receives committed tokens so it can exclude selected values. The field subscribes only to `model.structure` (committed tokens, empty or non-empty text, editor revision), so plain typing never re-runs the host render; in the host's own render read `model.state.peek()` and bind `model.result` (or a `computed`) as a signal hole — reading `model.state.value` there re-renders the host on every keystroke. Otherwise the app owns parsing and suggestions; call `readSearchField()` on input, empty `textContent` on clear, use `placeSearchFieldCaret()` after explicit controlled focus changes, and call `wireSearchFields()` once so Enter submits without inserting a line break and keyboard chip deletion restores focus plus the text-relative caret after controlled replacement. `clearIcon` replaces the clear action's glyph (its `clearLabel` still names it). For a compact icon button beside the clear button (a search-syntax help glyph), pass `trailingAction: { icon, label, action, expanded?, pressed?, controls?, haspopup?, attributes? }` — it matches the clear button's size, hover, focus, and cursor in every presentation; set `expanded` for a disclosure or `pressed` for a toggle, never both, and never hand-style a button into `trailing`. Enable `collapsible` for an animated iconic closed state, standalone or inside `ToolbarControlGroup`; the field keeps text or tokens expanded, and `wireSearchFields()` manages the transient expand/collapse/focus by default (activate to reveal + focus, Escape or empty blur to collapse). Bind the field's `expanded` to the returned handle's `expanded(id)` signal or adopt your own via `collapsible.signals`; opt a behavior out only when the app must own it. Do not hand-roll the open handler, the focusout collapse, a clear-button mousedown guard, or reopening after controlled clear or Select All deletion — the helper keeps the replacement editor open and focused. Clear and deletion restoration finish at the replacement mutation checkpoint before the next input task, so shortcut-letter typing cannot escape to the page and delayed animation frames cannot overwrite a later selection. Persist both query and tokens returned by the DOM read. For real-app adoption it also provides a focusout keep-open exception (`data-search-field-keep-open` regions or `collapsible.keepOpenOn(target)`, so a sibling suggestions/date/help surface does not collapse an empty field), opt-in atomic-chip keyboard (`keyboard`: Backspace/Delete remove the adjacent chip via `onRemoveToken`, ArrowRight moves the caret past a trailing chip), and an `onEdit({id, editor, event})` input callback (the `InputEvent` lets you gate on `inputType`/`data`) — reach for these instead of re-adding hand-rolled keydown/input handlers around the field.
|
|
372
375
|
An application helper can call `model.commit(value)` for the active `name:` prefix when it computes a valid value outside suggestions; invalid values and missing prefixes leave the query unchanged.
|
|
373
376
|
12. Demo work uses public production component subpaths and their browser-selected CSS. Focused demos and recipes have no local stylesheet, inline style, or styling-only class; use public components and `CatalogExample.viewport` for unavoidable specimen constraints (a recipe declares its frame through its exported `presentation`). To preview a top-layer specimen that production anchors with CSS anchor positioning, name the frame with `viewport.anchorName` (a dashed-ident such as `"--app-shell-main"`) and center in-flow content with `viewport.contentWidth` + `contentAlign: "center"` instead of wrapping the specimen in a stage element. When a recipe needs a capability no component offers, report the component gap instead of adding CSS. Use exactly three demo kinds: a `component` is one component with variants/states, a `composition` is a focused container of components commonly used together, and a `recipe` is the recommended production approach to a task. Organize catalog routes into product-important functional groups; within each group put all components before compositions, order each kind by importance with display-name alphabetical tie-breaking, and put recipes after every component/composition group. Show `Composition` as an entry tag instead of repeating the word in its name. 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 taxonomy, focused/composition decision, `CatalogExample` / `CatalogExampleStack` row/group nesting, specimen structure 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.
|
|
374
377
|
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 and framed disclosure regions). Plain Details use the 8px surface inset on the trigger and keep the body flush; plain Accordion items use it on both trigger and body. Outlined and sunken Details use the 16px container inset on the trigger and 8px on the body; framed Accordion items use 16px on both. 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. Filled badges use each variant's quiet fill with normal text so their compact copy stays WCAG-AA readable; override the pair with `--kui-wa-badge-filled-background` / `--kui-wa-badge-filled-foreground`, not host selectors. The theme draws a dialog's or drawer's built-in close control as the same 40px borderless circle a single `ToolbarControlGroup` button uses, so do not restyle it. 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.
|
|
375
|
-
14. Treat the
|
|
378
|
+
14. Kerf UI needs no Web Awesome. Treat the Web Awesome catalog as optional integration coverage, not a recommendation list; its UX sidebar marks every element a Kerf component replaces `Discouraged`, and `../docs/webawesome-migration.md` maps each to its Kerf replacement. Use `openAnchoredSurface` from `@kerfjs/ui/anchored-surface` for arbitrary interactive help or popup content beside a control, or `openAnchoredSurfaceAt` at pointer coordinates; pass an accessible label and let the app own content/actions. For a popup anchored to dark media chrome (a `ToolbarControlGroup tone="dark"`), pass `tone: 'dark'` instead of styling an app-owned dark panel: the surface takes that group's inverse tone and its content (a `ListItem`, a `wa-slider`) inherits the inverse text, border, and focus colors. It stays interactive inside a native `<dialog>`, `wa-dialog`, or `wa-drawer` (no host markup needed for the Web Awesome ones) and closes with that dialog. In an integration application, consider Popup only when its lower-level behavior is specifically needed, and use Tree/Tree Item, Animated Image, and Comparison only for a specific required behavior; avoid Zoomable Frame. Never render a `Discouraged` element in application, demo, or recipe markup; use the Kerf component that replaces it (`Button`, `TextField`, `Checkbox`, `RadioGroup`, `Switch`, `Select`, `PopupMenu`, `DialogSurface`, `SegmentedControl`, `TabBar`, `Chip`, `Badge`, `LucideIcon`, `ResizableRegion`), and treat a Kerf component gap as a request to file, not a reason to drop to the raw element.
|
|
376
379
|
15. Build sidebars, main areas, inspectors, and dialogs by rendering `Pane` (`@kerfjs/ui/pane`): an unpadded `.kui-pane` root, an optional `header`, one scrolling `.kui-pane__content`, and an optional `footer`; pick landmarks with `element` / `contentElement`. Never write a component's anatomy classes (`kui-pane`, `kui-toolbar`, `kui-list-item`, …) onto your own element to recreate it — render the component (`KUI-L103`). Only the catalog's `placeableClasses` (the `.kui-content` / `.kui-scroll-owner` / `.kui-control-cluster` / `.kui-inline-metadata` layout utilities, `.kui-app-root`, and `.kui-content-item` geometry on a non-div carrier) belong on elements you write. Use `Row` instead of a hand-written flex-row wrapper, `List` instead of a hand-written flex-column wrapper, and `Grid` instead of repeated equal-track CSS when children need a fixed positive count of evenly distributed columns. `Row` defaults to left/full with an `xs` gap and no wrapping; Grid defaults to an `xs` gap and keeps each track at `minmax(0, 1fr)`; List retains full/top and zero-gap defaults. The app owns Grid's responsive column-count changes. When a Row, List, or Grid is the layout root of a definite-height parent (an app root or a fixed-height frame), pass `fill` so it takes that full height, and put route/test/recipe `data-*` markers in its `rootAttributes`; never wrap a layout in a frame `Pane` just for height or a root attribute, because its content slot becomes a scroll owner that never scrolls. Inside a flex layout use `flex`, not `fill`. 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 render each ordinary surface-like child as `ContentItem` (`@kerfjs/ui/content-item`) for a child-owned 8px inline margin, 1px border, 8px padding, and 12px radius. For a chat transcript, set `bubble="start"` (other participants) or `bubble="end"` with `appearance="brand-loud"` (the person's own messages) instead of app width, alignment, or color CSS; the item fits its message up to 80% of the row. Choose `frame="framed"` (neutral border in the always-reserved 1px, same geometry) only for a real distinction and `shape="pill"` for 22px; do not hand-write `<div class="kui-content-item">` or its modifier classes. There is no filled variant: a lowered filled surface is `SunkenPanel`. Do not pad pane shells, duplicate item geometry in wrappers, or create nested scroll owners. Align text to text and icons to icons across a pane — a title, a row label, and bare prose share the same text column (`ListInsetText` puts bare text on it); a border is decoration and never the alignment anchor, so a bordered surface extends to the pane gutter instead of being indented to line its border up with a heading's text.
|
|
377
380
|
15a. Use `Text` from `@kerfjs/ui/text` for ordinary headings, paragraphs, and inline secondary text outside toolbar title zones. Omit `variant` for the default `p`, pass `h1`–`h6` according to the document outline, or use `variant="span"` inside a row, label, or table cell; ordinary native attributes pass through. Express supporting, error, compact, or code text with the independent finite `tone="quiet|danger"`, `size="compact"`, and `font="monospace"` props instead of global utility classes or appearance-only heading choices. Block Text variants own a transparent 1px border and the standard item padding; `span` owns no box geometry. Keep toolbar identity/title content on `ToolbarText` and list-aligned bare prose on `ListInsetText`. `ToolbarText` below `xlarge` is intentionally quiet identity text in default tone; a normal-color subsection title is `Text` with an `h2`–`h6` variant (or `ListHeader` for a list or menu section), never a recolored `ToolbarText`. Use `controlMargins` with canonical physical Sides for an exterior 8px control gutter without a Row wrapper; avoid stacking parent insets, and remember spans retain native inline margin behavior.
|
|
378
381
|
|
|
379
382
|
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.
|
|
380
383
|
16a. Dividers between pinned chrome and scrolling content are component chrome: a `Toolbar` draws none by default (`dividerSides` is for a permanent edge only). Call `wireScrollDividers(appRoot)` from `@kerfjs/ui/wire-scroll-dividers` once and retain its disposer; a plain `Pane` with `chromeDividers="scroll"` draws the line under its header only while content is scrolled beneath it and over its footer only while more content lies below (never when the content fits), every `NavStack`'s top chrome and bottom toolbar and every `TabNavigator` bar do the same around the active view or scene (keyed on whatever actually scrolls there, including a sole `Pane`'s content), and every `TabBar` marks each side of its strip with tabs scrolled out of view. Configure a pane with `chromeDividers` (`scroll` by default for plain panes, `always` by default for sunken panes, or explicit `scroll`, `always`, `none`, also via `PaneConfig`); `always` draws both lines even when content fits. A `NavStack` or `TabNavigator` has its own `chromeDividers` (same values: `always` draws the chrome lines without the wiring, `none` never, even when wired; a `SplitView` forwards it through `compactStack`); a `toolbarConfig.dividerSides` edge stays the Toolbar's own. Name app-owned chrome by id in `targets`. The wiring writes only `data-scroll-overflow` / `data-scroll-divider` state; never write a border onto a component to show a divider. For a header that floats over its scrolling content on translucent material, set `headerPresentation="overlay"` on the `Pane` (also in `PaneConfig`). The same wiring measures the header and publishes its height as block-start scroll clearance, which the content clears without a `scrollClearance` prop. Never set `--kui-scroll-clearance-block-start`, `--kui-pane-scrollbar-inline-size`, or a header height yourself; the header leaves a classic scrollbar uncovered on its own. An untoned `ToolbarText` keeps AA there by itself (normal-color title, lifted description); put other quiet secondary copy in the content. For an iOS-style translucent stack bar, set `chromePresentation="overlay"` on the `NavStack` (a `SplitView` forwards it through `compactStack`, a Workbench panel through `navStack`): `wireNavStack` measures the live top chrome and hands its height, top safe area included, to the views as their block-start inset, so a plain view or a sole `Pane` rests its first item below the bar with no prop; never pad a view to clear the bar yourself. An untoned `ToolbarText` in that chrome lifts its own title and description to AA, as in an overlay Pane header. For an iOS-style translucent bottom toolbar, set `bottomToolbarPresentation="overlay"` (a separate prop; `chromePresentation` covers only the top chrome, and `compactStack` / `navStack` forward both): `wireNavStack` measures the live bottom toolbar and hands its height, bottom safe area included, to the views as their block-end inset, so a plain view's or a sole `Pane`'s last item (or the Pane's footer) rests just above it with no prop. A default-tone `ToolbarControlGroup` in either overlaid bar lifts its controls to the normal text color by itself; never recolor them.
|
|
381
|
-
17. Each `Toolbar` zone accepts `KerfUiContent` directly, including status content, native controls, and application markup. Put identity/title text directly in the zone as `ToolbarText`; use `ToolbarControlGroup` when related controls need shared toolbar chrome, sizing, hover treatment, or overflow behavior. Bare controls keep their own styling and accessibility responsibilities. A standalone primary `wa-button` in trailing keeps its own Web Awesome chrome. `SegmentedControl`, `Select`, and `
|
|
384
|
+
17. Each `Toolbar` zone accepts `KerfUiContent` directly, including status content, native controls, and application markup. Put identity/title text directly in the zone as `ToolbarText`; use `ToolbarControlGroup` when related controls need shared toolbar chrome, sizing, hover treatment, or overflow behavior. Bare controls keep their own styling and accessibility responsibilities. A standalone primary `wa-button` in trailing keeps its own Web Awesome chrome. `SegmentedControl`, `Select`, and `SearchField` can live inside a group when they need that shared presentation. Configure zone behavior with `Toolbar.centerAlign` and its `responsive` / `responsiveAt` policy instead of selecting toolbar zone classes. No policy clips an action: trailing (and stacked center) zones wrap whole groups onto another row, and the center zone never compresses below its content (a leading title too long for the row truncates with an ellipsis instead), so never hide or shrink a toolbar control to make it fit; use `responsive="wrap"` for a heading whose title must stay whole, and move genuinely lower-priority commands into an app-owned "More" popup menu. 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. A popup menu in a toolbar is a `PopupMenu` (`@kerfjs/ui/popup-menu`, registered once via `@kerfjs/ui/popup-menu/register`) in a `single` group with `nestedDropdown` and a `menuInset`: give it typed `items` (commands with `action`s, `heading`s, `divider`s) and a trigger `icon` plus visible `text` or an accessible `label`; never hand-write `wa-dropdown` / `wa-dropdown-item` markup for a menu. Let the group size that trigger from its icon, localized text, and caret; do not force a width on the menu or button. By default a group stays on one row: each icon sits beside its label, and the toolbar's `responsive` policy relocates whole groups when needed. Set `overflow="wrap"` only for independent links that should move as whole actions to another row within the group; use `overflow="scroll"` for a bounded one-row action strip. A `ToolbarText` is one line ending in an ellipsis by default; opt into `wrap` (capped by `maxLines`) or `ellipsis={false}` deliberately. Inside a group, a `Select` or `PopupMenu` trigger drops its focus ring while its popup is open (the open popup and its current row show where focus is) and the ring returns when the popup closes; a ring follows the control's shape (a pill for an icon-and-caret trigger, a circle for a compact icon button) — set `focusRingOwner="group"` on a Select whose group paints the composed ring, and never add CSS to show, hide, or reshape a ring. 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 short supporting copy as the `ToolbarText` `description` (quiet, one line, aligned under the title, with `descriptionId` for `aria-describedby`; longer copy stays app-owned content below). A non-interactive heading icon is an icon tile: put the `LucideIcon` directly in its `ToolbarControlGroup` (it takes an icon button's 40px slot and centers), never inside a `<button>` that does nothing. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Each zone item sits in the toolbar's top 44px control band, centered by its own size; a taller item (a wrapped title, a second trailing row) starts at the band's top and grows down without moving the others, so a wrapped `ToolbarText` keeps trailing controls beside its first line — do not add alignment CSS for this. Split dormant and interactive regions: `ListHeader` fills the available inline width by default; set `width="content"` to shrink-wrap while retaining its normal geometry, or `inline` when it belongs in surrounding text without outer margin, border, or padding. It keeps its label and either a semantic count or independent `badge` and `status` indicators 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` inside a fitted 36px hover square whose transparent hit layer keeps the pointer target at least 44px (44px wide by the 36px row in `density="compact"`); never shrink the target to match the visual, and never force the button to 44px. Pass a sole non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a bare number in `badge`. For a contextual phrase such as "3 lines unpriced", `badge` can appear beside `status`, and either may be `undefined`. `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. Dividers are chrome and earn their place: a `Toolbar` draws none by default, and the line between pinned chrome and scrolling content comes from scroll state (rule 16a), so never add a border, a permanent `dividerSides`, or a margin to fake or adjust one.
|
|
382
385
|
For a toolbar heading that must remain at the 20px `--kui-font-l` step, use `ToolbarText size="xlarge-fixed"`; `xlarge` retains the responsive page-title clamp. Pass `headingLevel` when it is a section landmark.
|
|
383
386
|
|
|
384
|
-
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. For multi-line banner copy use `copyLayout="stacked"`; use `actionPlacement="below"` for an action that always takes its own trailing row (the default trailing action already wraps onto that row when a narrow banner leaves the copy too little room, so never switch placement per breakpoint). For a full-width status band at a layout edge (above a workspace), use `presentation="strip"` with optional `dividerSides` (bottom by default) instead of zeroing the card's margin, radius, and border tokens. For non-dismissible app-level progress with no home beside the affected work ("Loading…", "Updating… 3 of 12"), host the banner in `CornerStatus` and call `wireCornerStatus` once instead of fixed-position CSS or a toast with its close part hidden. For an icon-only `wa-button` with a `LucideIcon` (a Browse button beside a field), use `
|
|
387
|
+
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. For multi-line banner copy use `copyLayout="stacked"`; use `actionPlacement="below"` for an action that always takes its own trailing row (the default trailing action already wraps onto that row when a narrow banner leaves the copy too little room, so never switch placement per breakpoint). For a full-width status band at a layout edge (above a workspace), use `presentation="strip"` with optional `dividerSides` (bottom by default) instead of zeroing the card's margin, radius, and border tokens. For non-dismissible app-level progress with no home beside the affected work ("Loading…", "Updating… 3 of 12"), host the banner in `CornerStatus` and call `wireCornerStatus` once instead of fixed-position CSS or a toast with its close part hidden. For an icon-only `wa-button` with a `LucideIcon` (a Browse button beside a field), use an icon-only `Button` (`icon` plus a required `ariaLabel`, no label) rather than overriding `wa-button::part(base)` width, min-height, or padding. 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.
|
|
388
|
+
|
|
389
|
+
On compact devices, let a Pane's chrome give way through
|
|
390
|
+
`compactChrome: { header, footer }` (`scroll` puts it inline with the content,
|
|
391
|
+
`overlay` lays it over the content until `compactChromeOpen` opens it), passed
|
|
392
|
+
through a view's or panel's `pane` configuration; the NavStack view, tab
|
|
393
|
+
scene, full-screen dialog, or compact Workbench applies it. Call
|
|
394
|
+
`wirePaneChrome(root, { header, footer })` from `@kerfjs/ui/wire-pane-chrome`
|
|
395
|
+
with the open signals so overlay chrome closes on Escape or an outside press.
|
|
396
|
+
|
|
397
|
+
When a whole panel should accept dropped files, give its `FileDropZone` a
|
|
398
|
+
`dropScope` selector for the panel instead of setting `data-dragging` yourself.
|
|
399
|
+
|
|
400
|
+
For a status `Badge` that opens a menu, pass the Badge as a `PopupMenu`'s
|
|
401
|
+
`triggerContent` with `label` naming it, instead of wrapping it in an
|
|
402
|
+
application button.
|
|
403
|
+
|
|
404
|
+
For a count or dot on an icon control, pass `badge` (plus `badgeLabel` for its
|
|
405
|
+
meaning) to an icon-only `Button`, or put a `Badge placement="corner"` inside a
|
|
406
|
+
`ToolbarControlGroup` button and name the count in its `aria-label`; never
|
|
407
|
+
position a badge with application CSS.
|
|
408
|
+
|
|
409
|
+
For a page's content column, give the `List` `contentWidth` (`compact` 440px,
|
|
410
|
+
`medium` 640px, `wide` 864px; centered by default, `contentAlign="start"`
|
|
411
|
+
otherwise) instead of an application `max-width` wrapper; it composes with
|
|
412
|
+
`inset="document"`.
|
|
385
413
|
|
|
386
414
|
For compact `Select` triggers, set `triggerWidth` to `fit-content`, `max-content`,
|
|
387
|
-
or `fill` and
|
|
415
|
+
or `fill` and bound it with `maxTriggerWidth` / `minTriggerWidth` (any
|
|
416
|
+
presentation; `renderSelected` content ellipsizes before the caret). Style custom
|
|
388
417
|
selected content with the public selected color, font-size, and font-weight
|
|
389
418
|
tokens; plain text ellipsizes in a narrow trigger.
|
|
390
419
|
For a rail view heading, use `presentation="title"` for the 36px bold,
|
|
391
420
|
flush-start trigger and adjacent caret. Set `focusRingInset` when an ancestor
|
|
392
|
-
clips focus painting; never restyle Select
|
|
421
|
+
clips focus painting; never restyle Select's parts from application CSS.
|
|
393
422
|
|
|
394
|
-
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. 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. 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`, `Grid.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 Row/Grid/List flex, and a foreground color for choice icons: `uiColor()` with a `*-on-*` or text token (`success-on-quiet`, `text-quiet`) or `foregroundColorVar()`. Fill tokens (`*-fill-*`) are backgrounds, not foregrounds, and `SelectChoice.color` rejects them. 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". 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 `TabNavigator` (`@kerfjs/ui/tab-navigator`, iOS bottom tabs, each tab its own `NavStack`) for 2–5 co-equal sections on `compact`; show an unread count on a tab with its `badge` (a number or short string, rendered as the top-trailing danger `Badge` on the icon) plus a localized `badgeLabel` ("3 unread", folded into the tab's name as "Inbox, 3 unread"), never by concatenating the count into `label` or restyling the badge; for new content without a count pass `badge: true` (the text-free dot) with its required `badgeLabel` ("New activity"). 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. The stack's top chrome is a real `Toolbar`: put a view's title and groups on `NavStackView.toolbar` (`title`, `leading`, `center`, `trailing`), where its Toolbar configuration overrides stack-wide `toolbarConfig` defaults; the title leads, left-aligned and xlarge, at the root and steps down to large and centers (truncating before any control) once the back control shows, unless the view supplies `center` — do not restyle it to align; configure `backIcon` / `backText` through the stack, never with CSS. Each view and the stack fallback use a structured `bottomToolbar` with an accessible `label`; a view can also provide `header` / `footer`, `headerList` / `footerList`, and `pane` configuration, which render a Pane around that view's scrolling content. `SplitView` forwards both of its inner components: `resizable` takes the list `ResizableRegion`'s `separator`, `handleIcon`, `contentOverflow`, and collapse props (`collapsed`, `transitioning`, `collapseMotion`, `restoreControl`, `restorePosition`), and `compactStack` takes the compact stack's `toolbarConfig`, `backIcon`, `backText`, `hideToolbar`, `bottomToolbar`, plus `list` / `detail` view toolbars (structured `toolbar` and `bottomToolbar`). Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. Workbench panels are fixed-size unless a panel opts in with `resizable` (`true` or `{ min, max }`); then render `size={sizeSignal.value}` and call `wireWorkbench(root, { id, panels: { leftRail: { size: sizeSignal, storageKey? } }, deviceClass })` from `@kerfjs/ui/wire-workbench` for pointer drags, arrow/Shift+arrow/Home/End on the separator, persistence, and no resizing on `compact` classes. Inline rails, fixed or resizable, leave the work area `mainMinSize` px of width (a `Workbench` prop, default 320, `0` off), and an inline bottom drawer leaves it `mainMinHeight` px of height (default 120, `0` off): resizable panels stop growing there, rails shrink in proportion when the Workbench narrows and the drawer when it gets shorter, so do not add your own main-area `min-width` / `min-height`. Rails present as overlays below the `narrow` Workbench container breakpoint (704px) by default — `responsiveOverlayAt: "compact"` moves that to 448px and `"never"` keeps a rail inline — and a bottom drawer opts in the same way; do not derive `presentation` from `deviceClass`, and overlays never resize. At compact widths an overlay rail fills the Workbench less a 44px dismiss strip (`--kui-workbench-overlay-dismiss-margin`) on the side away from its edge; `compactOverlay: "full"` makes one rail fill it. Pass each such panel's `collapsed` signal to `wireWorkbench` too (`panels: { leftRail: { collapsed: navCollapsed } }`, `size` optional) so the overlay is transient like `wireSidebar`'s compact overlay: it collapses when the breakpoint begins to apply (the inline state returns when it ends), takes focus on its first control and keeps Tab inside it while open, and closes on Escape or an outside press with focus returned to its toggle (give each toggle `aria-controls` naming its panel's derived id, `<workbench id>-left-rail` / `-right-rail` / `-bottom-drawer`, so a panel open at wire-up has a focus target too); opening one overlay closes the other open overlays, so they never cover each other's controls; do not hand-roll those listeners (`dismissOverlays: false` opts out of all of it, `exclusiveOverlays: false` of exclusivity only). Collapse stays the app's `collapsed` flag and keeps the size, so an expanded panel returns at its last size. Let the Workbench compose panel toolbars: give each panel `toolbar: { label, title?, leading?, center?, trailing?, toggle: { action, name, showLabel?, hideLabel? } }` (`name` is the title-style panel name, such as "Navigator", which yields "Show Navigator" / "Hide Navigator") and the work area `mainToolbar` (and `mainBottomToolbar` for a drawer). Each of these toolbars takes the `Toolbar`'s configuration (`ToolbarConfig`: `dividerSides`, `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`); configure a composed toolbar through those props (for example `dividerSides: ""` to drop its divider), never with CSS. Configure the composed panes and chrome lists the same way: a panel's `pane` (and `CollapsiblePanel`'s `pane`) and the Workbench's `mainPane` take `PaneConfig` (`contentElement`, `contentLabel`, `separators`, `safeAreaEdges` — for example `{ contentElement: "nav", contentLabel: "Files" }` for a navigator rail), and `mainHeaderList` / `mainFooterList` take `ListConfig` (`gap`, `hAlign`, `vAlign`, `dividerSides`, `textInsets`, `controlInsets`). Open, a panel's toolbar places groups in its leading, center, and trailing zones, with the toggle last; mark a ToolbarControlGroup relocateOnCollapse to keep it available when closed (pass marked groups directly or in arrays rather than JSX fragments); closed, marked groups follow zone order into the work area before the toggle — a left rail's lead `mainToolbar`, a right rail's end it, a drawer's trail `mainBottomToolbar` or float in a corner `FloatingToolbar` — and panel-only groups stay behind. A control sits near where its effect appears: a collapsed drawer's restore belongs in the bottom toolbar or a `FloatingToolbar` beside the drawer, not in a distant top toolbar. Do not hand-place panel toggles in the work-area toolbar or render a second copy, and handle only the toggle's `data-action`. A collapsed panel renders `inert` and `aria-hidden` by itself, on the rail/drawer and its content (its `restoreControl` stays outside and reachable), so do not add your own `tabindex`, `aria-hidden`, or `inert` to hide it. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each stays out of the barrel, and its CSS-aware browser import loads the layout's CSS plus that of every component it renders (`Pane`, `Toolbar`, `List`, …), so do not hand-import `pane.css` or the other internal stylesheets; the companion `.css` subpath is only for pipelines without the `browser` condition. 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. Give a standalone panel the same `toolbar` roles and render `CollapsiblePanelRelocated` in your work-area toolbar (first in leading for a left rail, last in trailing for a right rail, a bottom toolbar or a `FloatingToolbar` restoreControl for a drawer) instead of hand-placing a second toggle. A collapsed `CollapsiblePanel`, `ResizableRegion`, or Workbench panel renders `inert` and `aria-hidden` by itself, so do not hide collapsed content yourself either. Likewise an open side overlay (a Workbench rail overlay, a `wireSidebar` compact overlay, an overlay `CollapsiblePanel` or horizontal `ResizableRegion`) hides the covered work area's `FloatingToolbar`s and restore corners by itself until it closes, so do not hide them from overlay state. A compact overlay opens only on a user action: wire-up on a compact device and a wide-to-compact crossing collapse every panel, and a wide crossing restores the inline state. Seed the signal with `signal(device.value.compact)` and pass the panel's `inlineCollapsed` default so the first compact render has no overlay. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). A list-detail dialog is a Workbench-shaped layout on roomy devices: a full-height list sidebar and a full-height main column, each a `Pane` with its own top `Toolbar`; the main column holds the primary xl title, the close control (`data-dialog="close"` in a `without-header` Web Awesome dialog), and the record actions, never a header spanning both columns. A top-level percentage-height shell needs a definite containing height: opt into `@kerfjs/ui/document.css` and put `.kui-app-root` on the one direct mount container, or provide equivalent application-owned geometry. The global baseline is deliberately absent from `styles.css` and component imports. See `docs/app-layouts.md`, `docs/document-baseline.md`, and `docs/collapsible-panel.md`. 23. Layouts and `Pane` are safe-area aware without configuration: backgrounds, separators, and dividers paint through a device's unsafe areas (a collapsible rail or drawer grows by the inset it docks to), content is padded only on the sides a region actually reaches, and block insets are scroll padding inside the scroller so first/last content can always be scrolled clear. Interior edges get nothing, and a collapsed rail hands its edge back to the center. Never add your own `env(safe-area-inset-*)` padding around a kerf layout or pane — that double-insets. In an app-owned arrangement of panes, pass `Pane.safeAreaEdges` (the sides that pane reaches; `[]` opts out); give an app bar or bottom bar that sits at a screen edge outside a Pane header or footer `Toolbar.safeAreaEdges` for the sides it reaches (for example `['block-start', 'inline-start', 'inline-end']`); the Workbench `mainToolbar` / `mainBottomToolbar` and panel `toolbar` configurations forward the same `safeAreaEdges` to their `Toolbar`. Do not set the internal `--kui-edge-inset-*` context yourself. The device insets are `--kui-safe-area-*` (defaulting to `env()`), overridable on `:root`. See `docs/app-layouts.md` "Safe areas".
|
|
423
|
+
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. 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. 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`, `Grid.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 Row/Grid/List flex, and a foreground color for choice icons: `uiColor()` with a `*-on-*` or text token (`success-on-quiet`, `text-quiet`) or `foregroundColorVar()`. Fill tokens (`*-fill-*`) are backgrounds, not foregrounds, and `SelectChoice.color` rejects them. 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". 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 `TabNavigator` (`@kerfjs/ui/tab-navigator`, iOS bottom tabs, each tab its own `NavStack`) for 2–5 co-equal sections on `compact`; show an unread count on a tab with its `badge` (a number or short string, rendered as the top-trailing danger `Badge` on the icon) plus a localized `badgeLabel` ("3 unread", folded into the tab's name as "Inbox, 3 unread"), never by concatenating the count into `label` or restyling the badge; for new content without a count pass `badge: true` (the text-free dot) with its required `badgeLabel` ("New activity"). 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. The stack's top chrome is a real `Toolbar`: put a view's title and groups on `NavStackView.toolbar` (`title`, `leading`, `center`, `trailing`), where its Toolbar configuration overrides stack-wide `toolbarConfig` defaults; the title leads, left-aligned and xlarge, at the root and steps down to large and centers (truncating before any control) once the back control shows, unless the view supplies `center` — do not restyle it to align; configure `backIcon` / `backText` through the stack, never with CSS. Each view and the stack fallback use a structured `bottomToolbar` with an accessible `label`; a view can also provide `header` / `footer`, `headerList` / `footerList`, and `pane` configuration, which render a Pane around that view's scrolling content. `SplitView` forwards both of its inner components: `resizable` takes the list `ResizableRegion`'s `separator`, `handleIcon`, `contentOverflow`, and collapse props (`collapsed`, `transitioning`, `collapseMotion`, `restoreControl`, `restorePosition`), and `compactStack` takes the compact stack's `toolbarConfig`, `backIcon`, `backText`, `hideToolbar`, `bottomToolbar`, plus `list` / `detail` view toolbars (structured `toolbar` and `bottomToolbar`). Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. Workbench panels are fixed-size unless a panel opts in with `resizable` (`true` or `{ min, max }`); then render `size={sizeSignal.value}` and call `wireWorkbench(root, { id, panels: { leftRail: { size: sizeSignal, storageKey? } }, deviceClass })` from `@kerfjs/ui/wire-workbench` for pointer drags, arrow/Shift+arrow/Home/End on the separator, persistence, and no resizing on `compact` classes. Inline rails, fixed or resizable, leave the work area `mainMinSize` px of width (a `Workbench` prop, default 320, `0` off), and an inline bottom drawer leaves it `mainMinHeight` px of height (default 120, `0` off): resizable panels stop growing there, rails shrink in proportion when the Workbench narrows and the drawer when it gets shorter, so do not add your own main-area `min-width` / `min-height`. Rails present as overlays below the `narrow` Workbench container breakpoint (704px) by default — `responsiveOverlayAt: "compact"` moves that to 448px and `"never"` keeps a rail inline — and a bottom drawer opts in the same way; do not derive `presentation` from `deviceClass`, and overlays never resize. At compact widths an overlay rail fills the Workbench less a 44px dismiss strip (`--kui-workbench-overlay-dismiss-margin`) on the side away from its edge; `compactOverlay: "full"` makes one rail fill it. Pass each such panel's `collapsed` signal to `wireWorkbench` too (`panels: { leftRail: { collapsed: navCollapsed } }`, `size` optional) so the overlay is transient like `wireSidebar`'s compact overlay: it collapses when the breakpoint begins to apply (the inline state returns when it ends), takes focus on its first control and keeps Tab inside it while open, and closes on Escape or an outside press with focus returned to its toggle (give each toggle `aria-controls` naming its panel's derived id, `<workbench id>-left-rail` / `-right-rail` / `-bottom-drawer`, so a panel open at wire-up has a focus target too); opening one overlay closes the other open overlays, so they never cover each other's controls; do not hand-roll those listeners (`dismissOverlays: false` opts out of all of it, `exclusiveOverlays: false` of exclusivity only). Collapse stays the app's `collapsed` flag and keeps the size, so an expanded panel returns at its last size. Let the Workbench compose panel toolbars: give each panel `toolbar: { label, title?, leading?, center?, trailing?, toggle: { action, name, showLabel?, hideLabel? } }` (`name` is the title-style panel name, such as "Navigator", which yields "Show Navigator" / "Hide Navigator") and the work area `mainToolbar` (and `mainBottomToolbar` for a drawer). Each of these toolbars takes the `Toolbar`'s configuration (`ToolbarConfig`: `dividerSides`, `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`); configure a composed toolbar through those props (for example `dividerSides: ""` to drop its divider), never with CSS. Configure the composed panes and chrome lists the same way: a panel's `pane` (and `CollapsiblePanel`'s `pane`) and the Workbench's `mainPane` take `PaneConfig` (`contentElement`, `contentLabel`, `separators`, `safeAreaEdges` — for example `{ contentElement: "nav", contentLabel: "Files" }` for a navigator rail), and `mainHeaderList` / `mainFooterList` take `ListConfig` (`gap`, `hAlign`, `vAlign`, `dividerSides`, `textInsets`, `controlInsets`). Open, a panel's toolbar places groups in its leading, center, and trailing zones, with the toggle last; mark a ToolbarControlGroup relocateOnCollapse to keep it available when closed (pass marked groups directly or in arrays rather than JSX fragments); closed, marked groups follow zone order into the work area before the toggle — a left rail's lead `mainToolbar`, a right rail's end it, a drawer's trail `mainBottomToolbar` or float in a corner `FloatingToolbar` — and panel-only groups stay behind. A control sits near where its effect appears: a collapsed drawer's restore belongs in the bottom toolbar or a `FloatingToolbar` beside the drawer, not in a distant top toolbar. Do not hand-place panel toggles in the work-area toolbar or render a second copy, and handle only the toggle's `data-action`. A collapsed panel renders `inert` and `aria-hidden` by itself, on the rail/drawer and its content (its `restoreControl` stays outside and reachable), so do not add your own `tabindex`, `aria-hidden`, or `inert` to hide it. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each stays out of the barrel, and its CSS-aware browser import loads the layout's CSS plus that of every component it renders (`Pane`, `Toolbar`, `List`, …), so do not hand-import `pane.css` or the other internal stylesheets; the companion `.css` subpath is only for pipelines without the `browser` condition. 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. Give a standalone panel the same `toolbar` roles and render `CollapsiblePanelRelocated` in your work-area toolbar (first in leading for a left rail, last in trailing for a right rail, a bottom toolbar or a `FloatingToolbar` restoreControl for a drawer) instead of hand-placing a second toggle. A collapsed `CollapsiblePanel`, `ResizableRegion`, or Workbench panel renders `inert` and `aria-hidden` by itself, so do not hide collapsed content yourself either. Likewise an open side overlay (a Workbench rail overlay, a `wireSidebar` compact overlay, an overlay `CollapsiblePanel` or horizontal `ResizableRegion`) hides the covered work area's `FloatingToolbar`s and restore corners by itself until it closes, so do not hide them from overlay state. A compact overlay opens only on a user action: wire-up on a compact device and a wide-to-compact crossing collapse every panel, and a wide crossing restores the inline state. Seed the signal with `signal(device.value.compact)` and pass the panel's `inlineCollapsed` default so the first compact render has no overlay. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). A modal list-detail task uses `ListDetailDialog` (`@kerfjs/ui/list-detail-dialog`), not a `DialogSurface` wrapped around a `SplitView`: its list and detail columns each run the dialog's full height, the list column carries a default-size `list.title` and `list.toolbar` controls, and the detail column carries the dialog's primary xl `detail.title`, its `detail.actions`, the close control, and a pinned `detail.footer` for record actions; pass `compact` from `deviceClass()` so it becomes a full-screen `NavStack` drill-down (the app owns selection and `detailActive`, and pops on `nav-back`). An editing task with side panels in a modal is `WorkbenchDialog` (`@kerfjs/ui/workbench-dialog`), a header-less dialog around a `Workbench`: its `title`, `actions`, and close control sit in the work area's toolbar (close stays outermost, after a hidden right rail's toggle), the rails keep their own toolbars, and `compact` makes the rails overlays that start collapsed; wire `wireWorkbench` on `<id>-workbench` with each rail's `collapsed` signal. Use `DialogSurface` for a single-pane dialog. A top-level percentage-height shell needs a definite containing height: opt into `@kerfjs/ui/document.css` and put `.kui-app-root` on the one direct mount container, or provide equivalent application-owned geometry. The global baseline is deliberately absent from `styles.css` and component imports. See `docs/app-layouts.md`, `docs/document-baseline.md`, and `docs/collapsible-panel.md`. 23. Layouts and `Pane` are safe-area aware without configuration: backgrounds, separators, and dividers paint through a device's unsafe areas (a collapsible rail or drawer grows by the inset it docks to), content is padded only on the sides a region actually reaches, and block insets are scroll padding inside the scroller so first/last content can always be scrolled clear. Interior edges get nothing, and a collapsed rail hands its edge back to the center. Never add your own `env(safe-area-inset-*)` padding around a kerf layout or pane — that double-insets. In an app-owned arrangement of panes, pass `Pane.safeAreaEdges` (the sides that pane reaches; `[]` opts out); give an app bar or bottom bar that sits at a screen edge outside a Pane header or footer `Toolbar.safeAreaEdges` for the sides it reaches (for example `['block-start', 'inline-start', 'inline-end']`); the Workbench `mainToolbar` / `mainBottomToolbar` and panel `toolbar` configurations forward the same `safeAreaEdges` to their `Toolbar`. Do not set the internal `--kui-edge-inset-*` context yourself. The device insets are `--kui-safe-area-*` (defaulting to `env()`), overridable on `:root`. See `docs/app-layouts.md` "Safe areas".
|
|
395
424
|
|
|
396
|
-
For application tabs, `
|
|
425
|
+
For application tabs, `TabBarItem presentation="icon-only"` retains its accessible `name`, `labelMaxWidth` truncates the visible name, and `attention` colors it through `--kui-tab-bar-item-attention-color`. Configure TabBar strip geometry through public `--kui-tab-bar-strip-*` tokens.
|
|
397
426
|
|
|
398
|
-
`TabBar presentation="inspector"` uses the shared rounded rectangle radius for its strip and inset segmented tabs. Ordinary `presentation="segmented"` keeps pill strip ends and smaller corners between tabs; do not restyle
|
|
427
|
+
`TabBar presentation="inspector"` uses the shared rounded rectangle radius for its strip and inset segmented tabs. Ordinary `presentation="segmented"` keeps pill strip ends and smaller corners between tabs; do not restyle TabBarItem to make one presentation match the other.
|
|
399
428
|
|
|
400
|
-
Set `
|
|
429
|
+
Set `TabBarItem dropTarget` while content is dragged over a tab to highlight the target without changing selection. Use `nameOverflow="visible"` for an inline loading treatment that needs the full visible name; the default is an ellipsis.
|
|
401
430
|
|
|
402
|
-
Set `pinned` on the first `
|
|
431
|
+
Set `pinned` on the first `TabBarItem` when it must stay visible while peers scroll. Keep it inside `TabBar` children so it remains in the tablist and roving focus order. Use `--kui-tab-bar-item-pinned-background` for a non-default surrounding surface; the pinned backdrop must cover tabs beneath it.
|
|
403
432
|
|
|
404
433
|
Use `--kui-resizable-region-background` for a region surface and `contentOverflow="visible"` while its anchored popup needs to escape. Choose a top or bottom `restorePosition`, typed `restoreInset`, or `restorePlacement="inline"` for its collapsed restore control. `FloatingToolbar placement="inline"` stays in normal flow; its per-edge inset tokens and `safeAreaInsets` configure floating placement.
|
|
405
434
|
|
|
@@ -415,7 +444,7 @@ Use `Toolbar responsive="trailing-priority"` when an expanded trailing search ne
|
|
|
415
444
|
|
|
416
445
|
For a 40px header embedded in a panel or card, pass `Toolbar density="compact"` (a 34px control band with 3px block insets) and give its groups `size="compact"`; never shrink a Toolbar with CSS. Workbench, CollapsiblePanel, NavStack, and SplitView toolbars take `density` through their `ToolbarConfig` and size the groups they compose (panel toggles, the back button) to match; pass `density` to `CollapsiblePanelRelocated` when the destination toolbar is compact. Set `--kui-toolbar-inset: 0px` on an embedded Toolbar to remove its outer padding while retaining `--kui-toolbar-gap` between zones. The same inset token controls the minimum-height allowance; safe-area padding still applies at claimed edges.
|
|
417
446
|
|
|
418
|
-
For compact dialog copy, use `Text flush lineHeight="tight"`; center short status copy with `Text align="center"` (or end-align a value with `align="end"`) rather than setting `text-align` on `.kui-text`; for compact metadata, use `ValueTable density="compact"` and its public row columns/padding/gap tokens. `ToolbarText size="xsmall"` fits a dense rail heading. A `
|
|
447
|
+
For compact dialog copy, use `Text flush lineHeight="tight"`; center short status copy with `Text align="center"` (or end-align a value with `align="end"`) rather than setting `text-align` on `.kui-text`; for compact metadata, use `ValueTable density="compact"` and its public row columns/padding/gap tokens. `ToolbarText size="xsmall"` fits a dense rail heading. A `SearchField` editor shrinks inside a narrow group by default; set `--kui-search-field-editor-min-width` when a minimum is needed. A full-height shell uses the one `.kui-app-root` mount container documented in `docs/document-baseline.md`.
|
|
419
448
|
When importing the opt-in `@kerfjs/ui/document.css` baseline, native `th`, `td`, `button`, `input`, `select`, and `textarea` inherit the surrounding font family, size, and line height; table headers retain their native emphasis. The baseline also gives native controls zero-specificity semantic cursors (pointer on enabled buttons, links, role=button, checkbox/radio inputs, and selects; not-allowed on disabled controls and a disabled `wa-button`; text on text inputs), so do not ship an app-wide cursor stylesheet. A full-viewport shell whose document must never scroll adds `data-viewport="locked"` to its body-child `.kui-app-root` (100dvh plus `overflow: clip` on `html`, `body`, and the root) instead of app CSS. Load application typography overrides after the baseline.
|
|
420
449
|
|
|
421
450
|
For a read-only field preview that replaces a Web Awesome input, use `FieldLabel` from `@kerfjs/ui/text` above the preview. Put it in `ListInsetControl sides="trl"` beside a `ContentItem` value; give it an id and use `aria-labelledby` on the preview group. The app owns preview/edit state and hint copy.
|
|
@@ -438,7 +467,7 @@ Common mistakes:
|
|
|
438
467
|
| Concatenate a section count into a ListHeader label or numeric badge | Pass `count={value}` and a localized full phrase such as `countLabel="3 notes"`; reserve `badge` for non-count SafeHtml |
|
|
439
468
|
| Put a chevron on navigation that reveals nothing | Remove it, or model a real controlled `ListHeader` disclosure; omit `actionIcon` to use the production `DisclosureArrow` |
|
|
440
469
|
| Test only a custom-element attribute | Assert live property, emitted event, focus, and rendered output |
|
|
441
|
-
| Import all Web Awesome components |
|
|
470
|
+
| Import all Web Awesome components | Kerf components need none of them. In an integration application, import only the individual Web Awesome modules it renders; `webawesome.css` registers no JavaScript |
|
|
442
471
|
| Write `wa-*` JSX without activating its types | Add `import type {} from '@kerfjs/ui/webawesome'`; registration and theme imports remain separate |
|
|
443
472
|
| Maintain a root list of component styles | Import each visual component from its JS subpath; its reachable CSS follows automatically |
|
|
444
473
|
| Add demo-only markup for a production state | Add the state to the production component, then render that export in the catalog |
|
|
@@ -446,7 +475,10 @@ Common mistakes:
|
|
|
446
475
|
| Add sidebar-specific wrapper padding | Use the unpadded `.kui-pane`; let `ListHeader`, `ListItem`, and `ContentItem` own their 8/1/8 geometry |
|
|
447
476
|
| Expect a bare toolbar control to receive group chrome automatically | Style and name the direct control itself, or use `ToolbarControlGroup` when controls need shared chrome |
|
|
448
477
|
| Reach for `wa-button` for a plain control inside a `ToolbarControlGroup` | Use a plain `<button>` (the group styles it fully); for a popup menu use `PopupMenu`, which renders its own trigger |
|
|
449
|
-
| Render a `
|
|
478
|
+
| Render a `wa-button` for a standalone action (a dialog footer, a form, a toolbar or tab bar action) | Use `Button` (`@kerfjs/ui/button`): `tone` `brand` for the main action, `danger` for a destructive one, `emphasis` `secondary` or `plain` for quieter ones; an icon-only `Button` (`icon` and `ariaLabel`) for an icon-only action, `pressed` for a toggle |
|
|
479
|
+
| Render a `wa-input` or `wa-textarea` for a form field | Use `TextField` (`@kerfjs/ui/text-field`): `label`, `hint`, and `error`; `multiline` with `rows` (and `maxRows` to grow) for a textarea; `clearable` and `showCount` with `wireTextFields(root)` |
|
|
480
|
+
| Render a `wa-checkbox`, `wa-radio-group`, or `wa-switch` | Use `Checkbox` (`indeterminate` with `wireCheckboxes(root)`), `RadioGroup` (`choices`, `value`, `name`), or `Switch` (only for a setting that applies immediately) |
|
|
481
|
+
| Render a `Discouraged` Web Awesome element (`wa-dropdown`, `wa-button-group`, `wa-select`, `wa-tab-group`, `wa-icon`, …) directly | Use the Kerf component that replaces it (`PopupMenu`, `SegmentedControl`, `Select`, `TabBar`, `LucideIcon`); file a component gap rather than dropping to the raw element. `KUI-L301` (`ui-preferences`, also run by `kerf-ui-doctor`) reports the raw tag and names the replacement |
|
|
450
482
|
| Let a toolbar group wrap, or stack an icon above its label | Keep icon and label on one row and let the group size to its content; relocate whole groups with the toolbar's `responsive` policy |
|
|
451
483
|
| Add CSS to show, hide, or reshape a Select or PopupMenu trigger's focus ring | Nothing: the ring follows the control's shape and drops while the popup is open; set `focusRingOwner="group"` when the group paints the composed ring |
|
|
452
484
|
| Add a border or a permanent `dividerSides` under pinned chrome to separate it from scrolling content | Call `wireScrollDividers(appRoot)` once: a `Pane` draws the line only while content is scrolled beneath it, and never pad below a divider to adjust it |
|
|
@@ -460,7 +492,7 @@ Common mistakes:
|
|
|
460
492
|
| Put an xl `ToolbarText` in a narrow rail or drawer header | Use the default size there as pane identity; xl truncates at the minimum size ("Ins…" in a 160px rail) |
|
|
461
493
|
| Force a `width`/`height`/`padding` on a component to size or space it | Let it size to its content and tokens; a forced box leaves a halo or a stretched oval — adjust an icon-size or spacing token, not the box |
|
|
462
494
|
| Wrap a component or region in a card, border, backdrop, or outline to "contain" it | Let it sit on the surface; add a `ContentItem frame="framed"` only for a real distinction — hierarchy comes from alignment, spacing, and type first |
|
|
463
|
-
| Override a component's `--kui-*` token to inset, size, or frame it | Use the typed prop: `FloatingToolbar inset`, `DisclosureArrow size`, `ListHeader headingLevel`, `
|
|
495
|
+
| Override a component's `--kui-*` token to inset, size, or frame it | Use the typed prop: `FloatingToolbar inset`, `DisclosureArrow size`, `ListHeader headingLevel`, `SearchField clearIcon`, `ContentItem frame="framed"` |
|
|
464
496
|
| Add another container's padding on top of a content-item's own margin | Pick one owner of the inset; a pane has no padding and its `.kui-content` children own the 8/1/8 geometry — stacking them double-insets |
|
|
465
497
|
| Put raw prose directly in a padded dialog body | Compose the body as `List`, set `bodyInset="none"` for list-owned geometry, and wrap bare prose in `ListInsetText` |
|
|
466
498
|
| Keep chrome, a label, or a readout that aids no decision | Delete it; every element must help a person decide or act |
|
|
@@ -468,11 +500,11 @@ Common mistakes:
|
|
|
468
500
|
|
|
469
501
|
For prominent content text, use `Text size="large"`; use `size="xlarge"` for display copy. Both sizes work with semantic `variant`, `tone`, and `font`, so keep heading levels based on the document outline.
|
|
470
502
|
|
|
471
|
-
For an exclusive choice among several visible options that must wrap (a labeled color palette), use `ChoiceChips` from `@kerfjs/ui/choice-chips`: separate pills backed by native radios, so the group is one Tab stop, arrow keys select, and the checked radio submits under `name`. Name it with `label` or `labelledBy` (a visible `FieldLabel`), lead choices with `swatch` (a `CssColor` such as `fillColor('#3b82f6')`, or `'transparent'` for the struck-through no-color dot) or `icon`, and handle the delegated `action` on `change`. Never wrap a `SegmentedControl` or button-appearance `wa-radio` set instead.
|
|
503
|
+
For an exclusive choice among several visible options that must wrap (a labeled color palette), use `ChoiceChips` from `@kerfjs/ui/choice-chips`: separate pills backed by native radios, so the group is one Tab stop, arrow keys select, and the checked radio submits under `name`. Name it with `label` or `labelledBy` (a visible `FieldLabel`), lead choices with `swatch` (a `CssColor` such as `fillColor('#3b82f6')`, or `'transparent'` for the struck-through no-color dot) or `icon`, and handle the delegated `action` on `change`. Never wrap a `SegmentedControl` or button-appearance `wa-radio` set instead. When any number of the options may be on, give `value` a `readonly Value[]` and pick a model. `multiple: 'toggle'` (label filters, annotation intents) makes the pills native checkboxes in a `role="group"`: each its own Tab stop, a click or Space toggles it alone, and every checked value submits under `name` (read the checked boxes by name in the change handler). `multiple: true` is the desktop multi-select: an `aria-multiselectable` listbox where a click selects one chip, Cmd (Apple) or Ctrl (elsewhere) toggles one, and Shift extends a range; it is one Tab stop with arrow, Shift+arrow, Space, and Cmd/Ctrl+A selection, and hidden inputs submit under `name`. It requires `wireChoiceChips(root)` from `@kerfjs/ui/wire-choice-chips`, which fires `change` on the group (the group carries the `action`); read the set with `readChoiceChips(group)`. A multiple group takes no `required`. On dark inverse chrome, put the chips in a dark appearance scope (`.wa-dark` or any `color-scheme: dark` ancestor) instead of restyling them.
|
|
472
504
|
|
|
473
505
|
For removable tags use `Chip`, with `removeAction` for delegated handling and a specific `removeLabel` for the native button. `itemId` identifies the tag; the app owns removal. `size="compact"` and `disabled` replace app styling of raw `wa-tag` internals. Use `Badge` for non-interactive metadata. For a downloadable file or a short link, give `Chip` an `href` (optional `download`, `true` or a file name, and `external`) instead of styling an `<a>`: it renders a native link with chip geometry, icon, truncation, focus ring, and hover feedback, and cannot also carry a remove button. Inside a `brand-loud` or `neutral` ContentItem bubble set `tone="inherit"`, which follows the bubble's foreground with a currentColor border and no fill. For a short value the user activates once (a filter token in a search help popover that inserts itself into the query), give `Chip` an `action` and `itemId`: the whole chip becomes a native `<button type="button" data-action data-item-id>` with hover, press, focus-ring, and disabled states, and an optional `actionLabel` that contains the visible label. It cannot also take `href` or `removeAction`. A compact rounded action chip keeps a compact `Badge`'s height in a compact `ValueTable` cell, so use it there instead of a `Badge` the app makes clickable.
|
|
474
506
|
|
|
475
|
-
For a chat message or assistant prompt, use `
|
|
507
|
+
For a chat message or assistant prompt, use `MessageComposer` from `@kerfjs/ui/message-composer`: a raised frame whose border turns brand while focus is within, a borderless textarea that grows between `minRows` (default 1) and `maxRows` (default 8) lines and then scrolls, and a compact `Toolbar` action row: optional `leading` controls (an attach button or model `Select` in a `ToolbarControlGroup`), an optional quiet `hint`, and `trailing` actions (a brand Send `Button`). Never hand-build that row; the composer owns its geometry. Name it with a title-style `label` and give it an `id` so the hint describes the field; the app reads and clears the textarea in its send action. For Enter-to-send, set `submitOnEnter` and call `wireMessageComposers(root)` from `@kerfjs/ui/wire-message-composers` once; Enter or Cmd/Ctrl+Enter clicks the first trailing action, Shift+Enter inserts a newline, and an IME composition commit or an empty message never sends. Never hand-roll that keydown handler. In a chat dialog, slot it into a `DialogSurface` with `footerPresentation="floating"` and `footerInset="none"`. Never wrap a `wa-textarea` and a button in app-styled composer chrome.
|
|
476
508
|
|
|
477
509
|
For an icon with a specific meaning, set `LucideIcon.color` using `uiColor('warning-on-quiet')` or another foreground token. Use `foregroundColorVar('--app-icon-color')` for an app-owned property or `foregroundColor('#8b5cf6')` for a direct custom CSS color. Omit the prop to inherit text color. Outline stroke and solid fill both follow the chosen color; size, appearance, label, and slot remain separate props. Avoid pale fill tokens as foregrounds.
|
|
478
510
|
Set `LucideIcon.inline` when an icon belongs within `Text` or another sentence; the SVG flows as `inline-block` instead of its default block layout. It retains its inherited 1em size unless you set `size`.
|
|
@@ -483,11 +515,12 @@ For nested action menus, use `PopupMenuItem.submenu` with item, heading, and div
|
|
|
483
515
|
|
|
484
516
|
## Select lifecycle ownership
|
|
485
517
|
|
|
486
|
-
Import `@kerfjs/ui/select/register` once. It
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
518
|
+
Import `@kerfjs/ui/select/register` once. It defines the form-associated
|
|
519
|
+
`kui-select` root and wires every Select: opening, top-layer placement,
|
|
520
|
+
keyboard navigation and typeahead, choosing, and dismissal. Listen for the
|
|
521
|
+
standard `input` / `change` events on the root and read its `value` (an array
|
|
522
|
+
with `multiple`). Do not add popup delays, position repairs, or animation
|
|
523
|
+
completion handlers in consumers.
|
|
491
524
|
|
|
492
525
|
Neutral lowered surfaces and decorative neutral fills/borders are translucent theme colors; compose nested public components directly. Default/raised canvases, foregrounds, focus indicators, and loud contrast-bearing fills remain opaque. Do not assign depth-specific component tokens in demo wrappers.
|
|
493
526
|
|
|
@@ -504,7 +537,7 @@ For a tab scene with drill-down, place a `NavStack` directly in `TabNavigatorTab
|
|
|
504
537
|
Use `Pane.deepInset` (also in `PaneConfig`) or `TabNavigatorTab.deepInset` for a plain scene when its scrolling content needs an extra 8px on all four sides, giving ordinary items a 16px inline gutter. The extra 8px is applied to the scroll surface, while nested Row/List/Grid item margins stay 8px. When a tab scene is a sole Pane or NavStack, configure deep inset on that child's Pane; the tab bar and top toolbar stay full width.
|
|
505
538
|
An edge-to-edge table or similar Pane child can read `--kui-pane-content-inset-inline-start` and `--kui-pane-content-inset-inline-end`: negate them for the child's logical margins, then add them to the child's text padding. The inherited values include routed safe areas and `deepInset`; keep the child in the Pane's single scroll slot. See `docs/app-layouts.md` for the CSS recipe.
|
|
506
539
|
|
|
507
|
-
Do not grow a collapsible search past the room beside its sibling groups: when it does not fit, the siblings wrap onto a new row while it opens. Size the expanded search to fit (`--kui-
|
|
540
|
+
Do not grow a collapsible search past the room beside its sibling groups: when it does not fit, the siblings wrap onto a new row while it opens. Size the expanded search to fit (`--kui-search-field-expanded-width`, or `sizing="grow"` with a fitting `growBasis`), and if a useful search still cannot fit, set aside the other controls while it is open, with `visibility="yield-to-expanded-sibling"` on those groups (they leave a 480px-or-narrower toolbar while a sibling is expanded) or behind a "…" `PopupMenu` overflow group; the app chooses which controls yield. Expanded search groups accept `expandedOverflow="visible"` to anchor app-owned suggestion/date/help surfaces below their relative root; collapsed/default groups retain existing clipping. Apps own surface placement/dismissal and use `data-search-field-keep-open` or `collapsible.keepOpenOn` for focus.
|
|
508
541
|
|
|
509
542
|
`ToolbarText` and `ToolbarControlGroup` accept typed `hideBelow`/`showBelow`
|
|
510
543
|
lengths (for example `px(416)`). Wire `wireToolbarVisibility(root)` from
|
|
@@ -519,8 +552,8 @@ details.
|
|
|
519
552
|
|
|
520
553
|
Select menus use native viewport shifting with 10px gutters: default menus grow to content (up to 20rem), while `fitMenu` matches trigger width and long labels wrap. Keep popup sizing and position repairs out of consumer CSS.
|
|
521
554
|
|
|
522
|
-
Give sibling `DialogSurface`s that render conditionally a stable `rootAttributes={{ "data-key": "…" }}` so a re-render morphs each dialog into itself (structurally identical roots otherwise match by position and swap names and open state); never wrap one in a keyed app `div`. Modal `DialogSurface` accepts a typed `preferredWidth` (`CssLength`) that overrides `size` while retaining the viewport width cap. Typed `viewportGutter` and `maxHeight` (`CssLength` or `"viewport"`) bound the modal; explicit height caps stay within the dynamic viewport minus both gutters. Omitted props
|
|
555
|
+
Give sibling `DialogSurface`s that render conditionally a stable `rootAttributes={{ "data-key": "…" }}` so a re-render morphs each dialog into itself (structurally identical roots otherwise match by position and swap names and open state); never wrap one in a keyed app `div`. Modal `DialogSurface` accepts a typed `preferredWidth` (`CssLength`) that overrides `size` while retaining the viewport width cap. Typed `viewportGutter` and `maxHeight` (`CssLength` or `"viewport"`) bound the modal; explicit height caps stay within the dynamic viewport minus both gutters. Omitted props keep the default caps (20px clear of each edge, 80% of the viewport height at 420px wide or narrower). Side-sheet/fullscreen geometry, application responsive choices, and preview layout remain separately owned. `height` (`CssLength` or `"viewport"`) gives the modal a definite bounded size, and `bodyLayout="fill"` hands the whole body to one child (a `SplitView` or `Pane`) that owns its scrolling. For a chat-style dialog, `bodyLayout="fill-last"` keeps leading rows (a notice) at their height and gives the last child (a `scrollable` `List` transcript) the rest, and `footerPresentation="floating"` lays the composer footer over the body, a typed `footerOffset` (default 16px) from the panel edges on a transparent background; the body reserves the measured footer height, so never add `::part(footer)` or `::part(body)` rules for it. To let the transcript scroll visibly beneath the floating composer, give the transcript `scrollClearance="block-end"` (on a `scrollable` `List` or a `Pane`): the body then publishes the footer's clearance as the inherited `--kui-scroll-clearance-block-end`, and the transcript adds it inside its own scroll area, so its last message still rests above the composer. Chrome that overlays a region's edge publishes `--kui-scroll-clearance-block-start` / `-end` the same way, and only the one scroller reaching that edge opts in (a composed Pane opts in through `PaneConfig.scrollClearance`: a Workbench `mainPane` or panel `pane`, a `CollapsiblePanel` `pane`, or a `NavStack` view's `pane`); never pad a transcript yourself to clear a footer. `headerPresentation="floating"` lays the dialog's title bar over the body's block start on the translucent `--kui-chrome-material-raised` (opaque under reduced transparency, increased contrast, forced colors, or without `backdrop-filter`); the open dialog measures it and the body reserves it. To let a transcript scroll beneath both the title bar and a floating composer, make it the body's only child (`bodyLayout="fill"`) with `scrollClearance="both"`: the body publishes the header's height as `--kui-scroll-clearance-block-start`. A `ToolbarText` as the `heading` keeps AA on the title bar by itself (normal-color title, lifted description); keep other quiet copy in the body. `presentation="bottom-sheet"` attaches the dialog to the bottom edge (iOS-style sheet): the top corners keep the dialog radius, the bottom corners are square, and the panel runs behind the home indicator and virtual keyboard while a block-end padding (the larger of `--kui-safe-area-block-end`, the `visualViewport` keyboard overlap `openDialog` measures, and `env(keyboard-inset-height)`) keeps content above them; `height` is its opening visible height and `maxHeight` its cap (the dynamic viewport minus the top safe area and one gutter). `resizable` adds a top grabber (a focusable "Resize sheet" separator; drag, arrows, Page Up/Down, Home/End) bounded by `minHeight` (default 160px) and the cap, restarting at `height` on each opening. It slides up on open and down on dismiss by default (`enterMotion` / `exitMotion` = `"standard"` keeps the dialog's scale-and-fade), with no motion under reduced motion. Never position a dialog at the bottom with your own CSS or add your own keyboard padding. For a non-modal, light-dismiss dialog use `PopoverDialogSurface` from the same subpath (native `popover="auto"`, `role="dialog"`, Kerf chrome, the same size/gutter/height/fill props, and `presentation="embedded"` for an in-flow catalog or inline preview) instead of a hand-built popover with app CSS. Name it with `label` or `labelledBy`, and describe it with `describedBy` (its `aria-describedby`), usually a title `ToolbarText`'s `descriptionId`.
|
|
523
556
|
|
|
524
|
-
|
|
557
|
+
Render a modal dialog as `DialogSurface` itself, a native `<dialog>`: `label` (or `labelledBy` with a `heading`, such as an xl `ToolbarText` with a description) names it, `children` is its body, `footer` holds its buttons, and `headerActions` sit before its close button (`withoutHeader` drops the header for content that owns its heading and close control). Open it with `openDialog(dialog)` and close it with `closeDialog(dialog)` from `@kerfjs/ui/surface-scaffold`; never toggle its `open` attribute. The close button, any `data-dialog-close` element inside (a Cancel button), Escape, and, with `lightDismiss`, a backdrop press dispatch a cancelable `kui-request-close` (`detail.source`: `close-button`, `escape`, or `backdrop`); cancel it to keep the dialog open (the panel pulses). The native `close` event fires once it closes. `openDialog` installs the listeners and, while the dialog is open, measures its floating header/footer, follows a bottom sheet's keyboard and grabber, and keeps that state across re-renders; import `@kerfjs/ui/surface-scaffold/register` only when a dialog may open some other way. Do not write `wa-dialog` inside a `DialogSurface`.
|
|
525
558
|
|
|
526
559
|
Toolbar visibility measurement probes and invisible state children are helper-owned and survive Kerf rerenders through `data-morph-preserve`. Actual CSS visibility stays stable while rendering removes transient parent markers, preventing geometry-driven render feedback and repeated probe recreation. Toolbar replacement, explicit internal-node removal and disposal retain their existing behavior.
|