bitboss-ui 3.0.0-beta.21 → 3.0.0-beta.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/README.md +55 -3
  2. package/bin/bitboss-ui-mcp.mjs +419 -9
  3. package/bin/bitboss-ui.mjs +293 -22
  4. package/dist/ai/BbAccordion.md +21 -23
  5. package/dist/ai/BbAlert.md +6 -12
  6. package/dist/ai/BbAvatar.md +60 -24
  7. package/dist/ai/BbBadge.md +12 -20
  8. package/dist/ai/BbBaseButton.md +14 -16
  9. package/dist/ai/BbBaseCheckboxIcon.md +6 -10
  10. package/dist/ai/BbBaseColorInput.md +1 -0
  11. package/dist/ai/BbBaseDatePickerInput.md +1 -0
  12. package/dist/ai/BbBaseNumberInput.md +1 -0
  13. package/dist/ai/BbBaseRadioIcon.md +6 -10
  14. package/dist/ai/BbBaseSelect.md +1 -0
  15. package/dist/ai/BbBaseSwitchIcon.md +5 -9
  16. package/dist/ai/BbBaseTag.md +1 -0
  17. package/dist/ai/BbBaseTextInput.md +2 -1
  18. package/dist/ai/BbBaseTextarea.md +1 -0
  19. package/dist/ai/BbBaseTimePickerInput.md +1 -0
  20. package/dist/ai/BbBreadcrumbs.md +23 -34
  21. package/dist/ai/BbButton.md +35 -47
  22. package/dist/ai/BbCheckbox.md +21 -37
  23. package/dist/ai/BbCheckboxGroup.md +13 -13
  24. package/dist/ai/BbCollapsible.md +14 -26
  25. package/dist/ai/BbColorInput.md +27 -23
  26. package/dist/ai/BbColorPalette.md +23 -35
  27. package/dist/ai/BbConfirm.md +17 -32
  28. package/dist/ai/BbDatePickerInput.md +29 -21
  29. package/dist/ai/BbDialog.md +159 -62
  30. package/dist/ai/BbDropdown.md +21 -25
  31. package/dist/ai/BbDropdownButton.md +13 -30
  32. package/dist/ai/BbDropzone.md +9 -11
  33. package/dist/ai/BbIcon.md +44 -33
  34. package/dist/ai/BbIndicator.md +2 -2
  35. package/dist/ai/BbNumberInput.md +32 -20
  36. package/dist/ai/BbOffCanvas.md +42 -42
  37. package/dist/ai/BbPagination.md +21 -23
  38. package/dist/ai/BbPopover.md +15 -17
  39. package/dist/ai/BbProgress.md +40 -51
  40. package/dist/ai/BbRadio.md +9 -9
  41. package/dist/ai/BbRadioGroup.md +7 -11
  42. package/dist/ai/BbRating.md +8 -12
  43. package/dist/ai/BbSelect.md +46 -36
  44. package/dist/ai/BbSelectPopover.md +19 -31
  45. package/dist/ai/BbSlider.md +15 -19
  46. package/dist/ai/BbSmoothHeight.md +16 -31
  47. package/dist/ai/BbSpinner.md +118 -74
  48. package/dist/ai/BbSwitch.md +25 -18
  49. package/dist/ai/BbSwitchGroup.md +9 -14
  50. package/dist/ai/BbTable.md +102 -55
  51. package/dist/ai/BbTabs.md +59 -82
  52. package/dist/ai/BbTag.md +26 -23
  53. package/dist/ai/BbTextInput.md +63 -27
  54. package/dist/ai/BbTextarea.md +42 -34
  55. package/dist/ai/BbTimePickerInput.md +14 -0
  56. package/dist/ai/BbToast.md +14 -34
  57. package/dist/ai/BbTooltip.md +22 -20
  58. package/dist/ai/BbTree.md +46 -72
  59. package/dist/ai/CommonInputWrapper.md +1 -0
  60. package/dist/ai/changelog.json +170 -62
  61. package/dist/ai/components.json +1678 -11
  62. package/dist/ai/composables/useConfirm.md +5 -2
  63. package/dist/ai/composables/useToast.md +4 -2
  64. package/dist/ai/guides/agent-contract.md +7 -4
  65. package/dist/ai/guides/ai-router.md +37 -22
  66. package/dist/ai/guides/component-picker.md +23 -18
  67. package/dist/ai/guides/design-language.md +151 -21
  68. package/dist/ai/guides/design-tokens.md +29 -4
  69. package/dist/ai/guides/icons-policy.md +46 -21
  70. package/dist/ai/guides/installation-and-plugin-setup.md +286 -17
  71. package/dist/ai/guides/migration/components/bb-rating.md +26 -1
  72. package/dist/ai/guides/migration/components/bb-tooltip.md +18 -0
  73. package/dist/ai/guides/migration/v2-to-v3.md +50 -23
  74. package/dist/ai/index.md +1 -1
  75. package/dist/ai/recipes/inertia/approvals-inbox.md +3 -3
  76. package/dist/ai/recipes/inertia/inline-edit-workspace.md +2 -2
  77. package/dist/ai/recipes/inertia/onboarding.md +2 -2
  78. package/dist/ai/recipes/inertia/ownership-atlas.md +6 -6
  79. package/dist/ai/recipes/inertia/record-form.md +5 -5
  80. package/dist/ai/recipes/inertia/records-workspace.md +4 -4
  81. package/dist/ai/recipes/inertia/upload-center.md +6 -6
  82. package/dist/ai/recipes/inertia/wizard-form.md +5 -5
  83. package/dist/ai/recipes/nuxt/approvals-inbox.md +3 -3
  84. package/dist/ai/recipes/nuxt/inline-edit-workspace.md +2 -2
  85. package/dist/ai/recipes/nuxt/onboarding.md +2 -2
  86. package/dist/ai/recipes/nuxt/record-form.md +5 -5
  87. package/dist/ai/recipes/nuxt/records-workspace.md +4 -4
  88. package/dist/ai/recipes/nuxt/upload-center.md +6 -6
  89. package/dist/ai/recipes/nuxt/wizard-form.md +5 -5
  90. package/dist/ai/recipes/vue/approvals-inbox.md +3 -3
  91. package/dist/ai/recipes/vue/inline-edit-workspace.md +2 -2
  92. package/dist/ai/recipes/vue/onboarding.md +2 -2
  93. package/dist/ai/recipes/vue/record-form.md +10 -10
  94. package/dist/ai/recipes/vue/records-workspace.md +4 -4
  95. package/dist/ai/recipes/vue/upload-center.md +6 -6
  96. package/dist/ai/recipes/vue/wizard-form.md +5 -5
  97. package/dist/ai/source/BbBadge.md +1 -1
  98. package/dist/ai/source/BbBaseCheckbox.md +1 -1
  99. package/dist/ai/source/BbBaseColorInput.md +7 -0
  100. package/dist/ai/source/BbBaseDatePicker.md +5 -5
  101. package/dist/ai/source/BbBaseDatePickerInput.md +9 -2
  102. package/dist/ai/source/BbBaseNumberInput.md +7 -0
  103. package/dist/ai/source/BbBaseRadio.md +1 -1
  104. package/dist/ai/source/BbBaseRating.md +5 -5
  105. package/dist/ai/source/BbBaseSelect.md +8 -1
  106. package/dist/ai/source/BbBaseSlider.md +5 -5
  107. package/dist/ai/source/BbBaseSwitch.md +1 -1
  108. package/dist/ai/source/BbBaseTag.md +7 -0
  109. package/dist/ai/source/BbBaseTextInput.md +7 -0
  110. package/dist/ai/source/BbBaseTextarea.md +7 -0
  111. package/dist/ai/source/BbBaseTimePickerInput.md +8 -1
  112. package/dist/ai/source/BbBreadcrumbs.md +20 -1
  113. package/dist/ai/source/BbButton.md +2 -2
  114. package/dist/ai/source/BbColorInput.md +14 -0
  115. package/dist/ai/source/BbDatePickerInput.md +14 -0
  116. package/dist/ai/source/BbDialog.md +39 -8
  117. package/dist/ai/source/BbDropdown.md +1 -1
  118. package/dist/ai/source/BbDropzone.md +5 -5
  119. package/dist/ai/source/BbIcon.md +23 -10
  120. package/dist/ai/source/BbNumberInput.md +14 -0
  121. package/dist/ai/source/BbOffCanvas.md +50 -28
  122. package/dist/ai/source/BbPagination.md +2 -2
  123. package/dist/ai/source/BbPopover.md +12 -1
  124. package/dist/ai/source/BbSelect.md +14 -0
  125. package/dist/ai/source/BbSelectPopover.md +45 -7
  126. package/dist/ai/source/BbSpinner.md +2 -2
  127. package/dist/ai/source/BbTable.md +11 -11
  128. package/dist/ai/source/BbTag.md +14 -0
  129. package/dist/ai/source/BbTextInput.md +14 -0
  130. package/dist/ai/source/BbTextarea.md +14 -0
  131. package/dist/ai/source/BbTimePickerInput.md +14 -0
  132. package/dist/ai/source/BbTooltip.md +57 -10
  133. package/dist/ai/source/ChipsBox.md +1 -1
  134. package/dist/ai/source/CommaBox.md +1 -1
  135. package/dist/ai/source/CommonInputWrapper.md +128 -33
  136. package/dist/ai/source/GuidesComponent.md +14 -7
  137. package/dist/ai/source/OptionsContainer.md +1 -1
  138. package/dist/components/BbBadge/BadgeBodyContent.vue_vue_type_script_setup_true_lang.js +1 -1
  139. package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +1 -1
  140. package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue_vue_type_script_setup_true_lang.js +1 -1
  141. package/dist/components/BbBaseColorInput/BbBaseColorInput.vue_vue_type_script_setup_true_lang.js +4 -1
  142. package/dist/components/BbBaseColorInput/types.d.ts +5 -0
  143. package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue_vue_type_script_setup_true_lang.js +4 -4
  144. package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue_vue_type_script_setup_true_lang.js +1 -1
  145. package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue_vue_type_script_setup_true_lang.js +5 -2
  146. package/dist/components/BbBaseDatePickerInput/types.d.ts +5 -0
  147. package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue.d.ts +1 -1
  148. package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue_vue_type_script_setup_true_lang.js +4 -1
  149. package/dist/components/BbBaseNumberInput/types.d.ts +5 -0
  150. package/dist/components/BbBaseRadio/BbBaseRadio.vue_vue_type_script_setup_true_lang.js +1 -1
  151. package/dist/components/BbBaseRating/BbBaseRating.vue_vue_type_script_setup_true_lang.js +3 -3
  152. package/dist/components/BbBaseSelect/BbBaseSelect.vue_vue_type_script_setup_true_lang.js +4 -1
  153. package/dist/components/BbBaseSelect/types.d.ts +5 -0
  154. package/dist/components/BbBaseSlider/BbBaseSlider.vue_vue_type_script_setup_true_lang.js +3 -3
  155. package/dist/components/BbBaseSwitch/BbBaseSwitch.vue_vue_type_script_setup_true_lang.js +1 -1
  156. package/dist/components/BbBaseTag/BbBaseTag.vue_vue_type_script_setup_true_lang.js +4 -1
  157. package/dist/components/BbBaseTag/types.d.ts +5 -0
  158. package/dist/components/BbBaseTextInput/BbBaseTextInput.vue_vue_type_script_setup_true_lang.js +4 -1
  159. package/dist/components/BbBaseTextInput/types.d.ts +5 -0
  160. package/dist/components/BbBaseTextarea/BbBaseTextarea.vue_vue_type_script_setup_true_lang.js +4 -1
  161. package/dist/components/BbBaseTextarea/types.d.ts +5 -0
  162. package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue_vue_type_script_setup_true_lang.js +5 -2
  163. package/dist/components/BbBaseTimePickerInput/types.d.ts +5 -0
  164. package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +71 -71
  165. package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +2 -2
  166. package/dist/components/BbColorInput/BbColorInput.vue_vue_type_script_setup_true_lang.js +5 -2
  167. package/dist/components/BbColorInput/types.d.ts +12 -0
  168. package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +4 -2
  169. package/dist/components/BbDatePickerInput/types.d.ts +12 -0
  170. package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +102 -97
  171. package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +1 -1
  172. package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +58 -58
  173. package/dist/components/BbDropzone/BbDropzone.vue_vue_type_script_setup_true_lang.js +2 -2
  174. package/dist/components/BbIcon/BbIcon.vue_vue_type_script_setup_true_lang.js +33 -35
  175. package/dist/components/BbIcon/iconCache.d.ts +30 -0
  176. package/dist/components/BbIcon/iconCache.js +21 -3
  177. package/dist/components/BbNumberInput/BbNumberInput.vue_vue_type_script_setup_true_lang.js +5 -2
  178. package/dist/components/BbNumberInput/types.d.ts +12 -0
  179. package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +57 -56
  180. package/dist/components/BbPagination/BbPagination.vue_vue_type_script_setup_true_lang.js +2 -2
  181. package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +138 -138
  182. package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +4 -2
  183. package/dist/components/BbSelect/types.d.ts +12 -0
  184. package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +43 -43
  185. package/dist/components/BbSpinner/BbSpinner.vue_vue_type_script_setup_true_lang.js +1 -1
  186. package/dist/components/BbSpinner/types.d.ts +1 -1
  187. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +8 -8
  188. package/dist/components/BbTable/BbTableDataRow.js +2 -2
  189. package/dist/components/BbTag/BbTag.vue_vue_type_script_setup_true_lang.js +5 -2
  190. package/dist/components/BbTag/types.d.ts +12 -0
  191. package/dist/components/BbTextInput/BbTextInput.vue_vue_type_script_setup_true_lang.js +5 -2
  192. package/dist/components/BbTextInput/types.d.ts +12 -0
  193. package/dist/components/BbTextarea/BbTextarea.vue_vue_type_script_setup_true_lang.js +5 -2
  194. package/dist/components/BbTextarea/types.d.ts +12 -0
  195. package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +4 -2
  196. package/dist/components/BbTimePickerInput/types.d.ts +12 -0
  197. package/dist/components/BbToast/BbToastRegion.vue_vue_type_script_setup_true_lang.js +62 -62
  198. package/dist/components/BbTooltip/BbTooltip.vue_vue_type_script_setup_true_lang.js +81 -78
  199. package/dist/components/BbTooltip/types.d.ts +8 -1
  200. package/dist/components/ChipsBox/ChipsBox.vue_vue_type_script_setup_true_lang.js +1 -1
  201. package/dist/components/CommaBox/CommaBox.vue_vue_type_script_setup_true_lang.js +1 -1
  202. package/dist/components/CommonInputWrapper/CommonInputWrapper.vue.d.ts +1 -0
  203. package/dist/components/CommonInputWrapper/CommonInputWrapper.vue_vue_type_script_setup_true_lang.js +4 -2
  204. package/dist/components/CommonInputWrapper/types.d.ts +10 -0
  205. package/dist/components/OptionsContainer/OptionsContainer.vue_vue_type_script_setup_true_lang.js +1 -1
  206. package/dist/composables/useConfirm.d.ts +22 -0
  207. package/dist/composables/useConfirm.js +43 -40
  208. package/dist/composables/useSafeAreaInternal.js +1 -1
  209. package/dist/composables/useToast.d.ts +16 -0
  210. package/dist/composables/useToast.js +20 -16
  211. package/dist/composables/useViewportWidth.d.ts +20 -6
  212. package/dist/composables/useViewportWidth.js +8 -7
  213. package/dist/icon-scan.d.ts +276 -0
  214. package/dist/icon-scan.js +485 -0
  215. package/dist/icons-whitelist.d.ts +5 -4
  216. package/dist/index.d.ts +2 -0
  217. package/dist/input-variants.d.ts +18 -0
  218. package/dist/llms-full.txt +2173 -1537
  219. package/dist/llms-medium.txt +504 -82
  220. package/dist/llms.txt +1 -1
  221. package/dist/nuxt-module.d.ts +1 -0
  222. package/dist/nuxt.js +10 -7
  223. package/dist/plugin.js +1 -0
  224. package/dist/runtime/nuxt-plugin.js +2 -1
  225. package/dist/styles.css +1 -1
  226. package/dist/types/Config.d.ts +18 -0
  227. package/dist/types/InputVariant.d.ts +22 -0
  228. package/dist/utils/cssLayers.d.ts +91 -0
  229. package/dist/utils/cssLayers.js +43 -0
  230. package/dist/utils/injectLibraryStyles.d.ts +3 -1
  231. package/dist/utils/injectLibraryStyles.js +24 -23
  232. package/dist/validated/BbColorInput.vue_vue_type_script_setup_true_lang.js +1 -0
  233. package/dist/validated/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +1 -0
  234. package/dist/validated/BbNumberInput.vue_vue_type_script_setup_true_lang.js +1 -0
  235. package/dist/validated/BbSelect.vue_vue_type_script_setup_true_lang.js +1 -0
  236. package/dist/validated/BbTag.vue_vue_type_script_setup_true_lang.js +1 -0
  237. package/dist/validated/BbTextInput.vue_vue_type_script_setup_true_lang.js +1 -0
  238. package/dist/validated/BbTextarea.vue_vue_type_script_setup_true_lang.js +1 -0
  239. package/dist/validated/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +1 -0
  240. package/dist/validated/index.d.ts +1 -0
  241. package/dist/vite-plugin.d.ts +89 -12
  242. package/dist/vite.js +331 -281
  243. package/llms.txt +1 -1
  244. package/package.json +7 -5
  245. package/scripts/lib/ensure-manifest.mjs +28 -5
  246. package/scripts/lib/eslint-plugin.d.ts +35 -0
  247. package/scripts/lib/eslint-plugin.mjs +299 -3
  248. package/scripts/lib/hand-roll-hints.mjs +50 -6
  249. package/scripts/lib/html-attributes.mjs +42 -0
  250. package/scripts/lib/mcp-config.mjs +38 -34
  251. package/scripts/lib/public-type-exports.mjs +246 -0
  252. package/scripts/lib/validate-bb-markup.mjs +205 -12
@@ -17,8 +17,11 @@ Per the design language, every `destructive` action must pass through a confirm.
17
17
  **Requires exactly one `<BbConfirm />` host, mounted at the app root** (`App.vue`
18
18
  under Vue, `app.vue` under Nuxt, the `createInertiaApp` render root under
19
19
  Inertia) — never one per page or per component. With no host, `await confirm(...)`
20
- never settles and the caller hangs silently. With two hosts, every dialog renders
21
- twice. See [Agent Contract § Global Host Rule](../guides/agent-contract.md).
20
+ never settles and the caller hangs; with two, every dialog renders twice. Both
21
+ warn in DEV, including when the only host has unmounted. The no-host warning is
22
+ deferred by a second, because a late-mounting host renders the pending dialog
23
+ and settles the promise. See
24
+ [Agent Contract § Global Host Rule](../guides/agent-contract.md).
22
25
 
23
26
  ## Import
24
27
 
@@ -16,8 +16,10 @@ per viewport corner and disappear on their own (default **4000 ms**).
16
16
  under Vue, `app.vue` under Nuxt, the `createInertiaApp` render root under
17
17
  Inertia) — never one per page or per component. With no host, messages are
18
18
  enqueued but never rendered _and never expire_, because the dismiss timer lives
19
- inside the host. With two hosts, every message renders once per host. See
20
- [Agent Contract § Global Host Rule](../guides/agent-contract.md).
19
+ inside the host. With two hosts, every message renders once per host. Both warn
20
+ in DEV, including when the only host has unmounted. The no-host warning is
21
+ deferred by a second, because a late-mounting host renders the queued message.
22
+ See [Agent Contract § Global Host Rule](../guides/agent-contract.md).
21
23
 
22
24
  Rule of thumb: `useToast` for _what just happened_ ("Saved", "Upload complete"),
23
25
  `BbAlert` for the _current state of the page_ (a form error summary, a banner).
@@ -12,6 +12,8 @@ Use this contract before generating any UI code with `bitboss-ui`.
12
12
  1. Prefer `Bb*` components in consumer-facing code.
13
13
  2. Treat low-level primitives (`Base*`, `BbBase*`, `Common*`, listbox internals) as reference-only — **except `BbBaseButton`**, which is a first-class consumer component: the unstyled navigable primitive for any clickable surface (cards, rows, custom links) that isn't a variant-first `BbButton`.
14
14
  3. Use the other reference-only components only when the user explicitly asks for internal composition details.
15
+ 4. **Not every `Bb*` export is a component.** `BbTableColumn`, `BbDropdownItem`, `BbTabsItem` and `BbBreadcrumbsItem` are TYPES: they describe the objects you pass to a prop (`columns`, `items`), and they never render. Import one with `import type { … } from 'bitboss-ui'`; writing `<BbTableColumn>` is always wrong. The `types` array in `components.json` lists every one with the prop it belongs to, and `npx bitboss-ui check` says so by name if you slip.
16
+ 5. **Two dozen of those types are the ones to reach for.** Each carries a `usage` tier: `api` means it appears in a component's own props or events, so it is what you annotate the value you build — `get_component` returns the list for the component you asked about. `wrapper` (`BbButtonProps` / `BbButtonEvents` / `BbButtonSlots` and siblings) is only for wrapping one of our components in one of yours, and `advanced` is everything left over — contexts, directive values, config shapes, the `…VariantType` unions. The tier is decided by that one rule and nothing else, so do not sort by what a name sounds like: `AlertVariantRegistry` reads like a registry but is `api`, because `BbAlert`'s `variant` prop is typed by it. Only `api` entries carry `usedBy`; if a type has one, it is `api`. Types the library does NOT export are that way on purpose: a size or a trigger is a string literal you write directly (`size="lg"`), so there is no name to import and none is missing.
15
17
 
16
18
  ## Setup-First Rule
17
19
 
@@ -113,7 +115,7 @@ For components accepting `items`:
113
115
 
114
116
  ## Icons Rule
115
117
 
116
- External-provider-first: prefer iconify provider icons — default set `lucide:*` — sourced from `@iconify-json/*` packages the consumer installs separately (`npm i -D @iconify-json/lucide`). Use `local:*` SVGs only for brand-specific art placed in the configured `iconDir`. Only reference icons from installed providers or existing local files — **never invent icon names**. Full workflow: [Icons Policy](./icons-policy.md).
118
+ External-provider-first: prefer iconify provider icons — default set `lucide:*` — sourced from `@iconify-json/*` packages the consumer installs separately (`npm i -D @iconify-json/lucide`). Use `local:*` SVGs only for brand-specific art placed in the configured `iconDir`. Only reference icons from installed providers or existing local files — **never invent icon names**. Provider icons ship only when written as a `prefix:name` literal somewhere in the project (any file type, PHP included) or listed in the plugin's `iconWhitelist`; the same rule applies in dev, and a name that does not resolve renders a placeholder and logs an error instead of throwing. Full workflow: [Icons Policy](./icons-policy.md).
117
119
 
118
120
  ## Variants Rule
119
121
 
@@ -154,8 +156,9 @@ These overlays also return focus to their trigger element on close, by documente
154
156
 
155
157
  **Mount exactly one `<BbToast />` and one `<BbConfirm />` for the whole application, at the app root** — `App.vue` under Vue, `app.vue` under Nuxt, the `createInertiaApp` render root under Inertia. Never one per page, per route, per layout branch or per component; a page that calls `toast()` or `confirm()` mounts nothing.
156
158
 
157
- - **Zero hosts** — `toast(...)` enqueues silently and the message never expires (the dismiss timer lives inside the host); `await confirm(...)` never settles, so the caller hangs. Neither throws.
158
- - **Two hosts** — every toast and every dialog renders once per host: duplicated DOM, duplicated `role="status"` live regions, duplicated focus traps. `BbToast` warns in DEV; `BbConfirm` does not warn at all.
159
+ - **Zero hosts** — `toast(...)` enqueues and the message never expires (the dismiss timer lives inside the host); `await confirm(...)` never settles, so the caller hangs. Neither throws. Both warn once per process in DEV.
160
+ - **Two hosts** — every toast and every dialog renders once per host: duplicated DOM, duplicated `role="status"` live regions, duplicated focus traps. Both warn in DEV, naming the host count.
161
+ - **A host that mounts and then unmounts also warns** (since 2026-09-09). This is the failure mode of mounting a host anywhere but the app root: one in a layout works right up until a route renders outside that layout, and the promise then hangs forever. The warning is **deferred by one second**, not immediate, because a late-mounting host renders the pending dialog and settles the promise normally — so a host that unmounts and remounts across a route change is not a failure and stays quiet. What the warning means is "one second later, still nothing to render this".
159
162
 
160
163
  Standalone examples in this repo (`src/components/*/examples/*.vue`, stories, dev showcases) mount a host inline **because each one is a self-contained root with no application around it**. That is a property of the example harness, not a pattern to copy — in a real app the host is already mounted at the root, and the page must not mount a second one.
161
164
 
@@ -168,7 +171,7 @@ Boolean props default to `false` so the bare `<Comp foo>` shorthand turns the fe
168
171
 
169
172
  These are the only intentional default-`true` booleans; anywhere else, `true` default is a bug (`check-api` flags it).
170
173
 
171
- **Arrows are tooltip-only in v3.** `hideArrow` / `showArrow` / `arrowPadding` were removed from `BbPopover`, `BbSelectPopover`, `BbColorPalette`, and `BbDropdown` (leftover bindings land silently in `$attrs`). Only `BbTooltip` keeps `hideArrow` (default `false` — arrow shown). See [bb-popover.md](./migration/components/bb-popover.md).
174
+ **Arrows are tooltip-only in v3.** `hideArrow` / `showArrow` / `arrowPadding` were removed from `BbPopover`, `BbSelectPopover`, `BbColorPalette`, and `BbDropdown` (leftover bindings land silently in `$attrs`). `BbTooltip` keeps **both** — `hideArrow` (default `false`, so the arrow shows) and `arrowPadding` (default `10`). See [bb-popover.md](./migration/components/bb-popover.md).
172
175
 
173
176
  ## Testing Against bitboss-ui
174
177
 
@@ -37,7 +37,8 @@ pinned to the prerelease channel rather than following it.
37
37
  `locale` explicitly if the app is not Italian** — the plugin defaults to
38
38
  `'it'` and there is no build-time warning for leaving it unset.
39
39
  3. **Choose intent:**
40
- - Whole page / flow → hop to **Recipes** below (one platform file only).
40
+ - Whole page / flow → hop to **Recipes** below (one platform folder: the
41
+ layout scaffold plus your page recipe).
41
42
  - Single control → [Component picker](./component-picker.md), then open
42
43
  **one** `../<ComponentName>.md` contract (Usage & Guidelines first).
43
44
  4. **Never invent props.** Confirm names in that contract or in
@@ -51,16 +52,27 @@ Folders: `../recipes/vue/`, `../recipes/nuxt/`, `../recipes/inertia/`.
51
52
 
52
53
  Same page shapes on each platform; only the data/mutation layer differs.
53
54
 
54
- | Intent | Recipe file (kebab) |
55
- | ------------------------------------ | ----------------------------------------- |
56
- | Filterable listing + bulk/peek | `records-workspace.md` |
57
- | Create/edit form | `record-form.md` |
58
- | Inline-edit table / backlog | `inline-edit-workspace.md` |
59
- | Hierarchy drill-down (nested tables) | `ownership-atlas.md` (Inertia only today) |
60
- | Approvals / triage inbox | `approvals-inbox.md` |
61
- | Uploads + editable queue | `upload-center.md` |
62
- | Multi-step wizard | `wizard-form.md` |
63
- | Onboarding checklist | `onboarding.md` |
55
+ **`layout-scaffold.md` is a prerequisite, not an option.** It is the three
56
+ app-owned chrome files — the peek-stack context, the `PageShell` frame and the
57
+ `FloatingBottomPanel` selection dock — that every other recipe's
58
+ `@/components/layout/*` imports resolve to. Open it **with** your page recipe;
59
+ the pair counts as one hop.
60
+
61
+ | Intent | Recipe file (kebab) |
62
+ | ----------------------------------------- | ----------------------------------------- |
63
+ | App chrome every recipe imports (**1st**) | `layout-scaffold.md` |
64
+ | Filterable listing + bulk/peek | `records-workspace.md` |
65
+ | Create/edit form | `record-form.md` |
66
+ | Inline-edit table / backlog | `inline-edit-workspace.md` |
67
+ | Hierarchy drill-down (nested tables) | `ownership-atlas.md` (Inertia only today) |
68
+ | Approvals / triage inbox | `approvals-inbox.md` |
69
+ | Uploads + editable queue | `upload-center.md` |
70
+ | Multi-step wizard | `wizard-form.md` |
71
+ | Onboarding checklist | `onboarding.md` |
72
+ | ⌘K / Ctrl+K global command palette | `command-palette.md` |
73
+
74
+ That table is the complete set — every file in each platform folder appears in
75
+ it, so a directory listing (or the MCP `list_recipes`) can add nothing.
64
76
 
65
77
  No recipe for detail-with-tabs, settings, dashboards, or profile pages —
66
78
  compose those from the closest listing/form recipe plus the guides
@@ -70,17 +82,20 @@ compose those from the closest listing/form recipe plus the guides
70
82
 
71
83
  ## Playbooks (only when the pattern applies)
72
84
 
73
- | Need | Guide |
74
- | -------------------------------------- | ----------------------------------------------------- |
75
- | Tied selects / stale model cleanup | [coherence-playbook](./coherence-playbook.md) |
76
- | Async `items` providers / prefill | [fetch-items-playbook](./fetch-items-playbook.md) |
77
- | `item-text` / `item-value` / disabled | [options-items-playbook](./options-items-playbook.md) |
78
- | Table cell edit ownership | [inline-edit-playbook](./inline-edit-playbook.md) |
79
- | Table row → detail (identity cells) | [agent-contract](./agent-contract.md) § Inline Links |
80
- | Peek that drills into related records | [agent-contract](./agent-contract.md) § Consult-Stack |
81
- | Icons (`lucide:*`, never invent names) | [icons-policy](./icons-policy.md) |
82
- | Tokens / styling around components | [design-language](./design-language.md) |
83
- | Upgrading from bitboss-ui 2.x | [migration/v2-to-v3](./migration/v2-to-v3.md) |
85
+ | Need | Guide |
86
+ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
87
+ | Tied selects / stale model cleanup | [coherence-playbook](./coherence-playbook.md) |
88
+ | Async `items` providers / prefill | [fetch-items-playbook](./fetch-items-playbook.md) |
89
+ | `item-text` / `item-value` / disabled | [options-items-playbook](./options-items-playbook.md) |
90
+ | Table cell edit ownership | [inline-edit-playbook](./inline-edit-playbook.md) |
91
+ | Table row → detail (identity cells) | [agent-contract](./agent-contract.md) § Inline Links |
92
+ | Peek that drills into related records | [agent-contract](./agent-contract.md) § Consult-Stack |
93
+ | Icons (`lucide:*`, never invent names) | [icons-policy](./icons-policy.md) |
94
+ | Tokens / styling around components | [design-language](./design-language.md) |
95
+ | Theming a whole app / retuning a component | [design-tokens](./design-tokens.md) — the two tiers, and why most restyling is a component token, not a `--bb-*` one |
96
+ | A Tailwind utility on a `Bb*` component does nothing, or half-applies | [installation](./installation-and-plugin-setup.md) § Cascade layers — v4 is layered, we are not by default. It is per PROPERTY: a utility loses only where we declare the same one, and a shorthand like `p-4` can half-apply. `cssLayers: TAILWIND_V4_LAYERS` in the build plugin |
97
+ | Upgrading from bitboss-ui 2.x | [migration/v2-to-v3](./migration/v2-to-v3.md) |
98
+ | Bumping between two v3 prereleases | `npx bitboss-ui changelog --since <your version>` — what BREAKS in the bump; then `check --manifest` for the files it breaks |
84
99
 
85
100
  ---
86
101
 
@@ -231,24 +231,29 @@ a composable you want is not in this corpus's `composables/` folder — it is no
231
231
  here.** Check this list before inventing a name; if the need is genuinely absent,
232
232
  compose it or write it yourself.
233
233
 
234
- | Assumed component or composable | Reality |
235
- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
236
- | `BbColumnManager` — show/hide/reorder columns | Does not exist as a component. Order is `v-model:order` on `BbTable` (an array of keys any panel can write; `reorderable` adds the built-in header drag + keyboard handle); visibility is `hidden: true` on the column definition (the column stays declared and keeps its slot in the order; header slots get a `hideColumn()` that fires `hide:column`). Compose the panel from `BbPopover` + `BbCheckbox`. |
237
- | `BbKanban` / drag-and-drop board | Does not exist. There is no reusable DnD primitive in the library (`BbTable`'s header drag is internal to the table) — compose the board from `BbTable` (a table/list view of the same data) or hand-roll the drag-and-drop interaction yourself. |
238
- | `BbModal` | It is [BbDialog](../BbDialog.md). |
239
- | `BbDrawer` / `BbSheet` | It is [BbOffCanvas](../BbOffCanvas.md). |
240
- | `BbCombobox` / `BbAutocomplete` | It is [BbSelect](../BbSelect.md) / [BbSelectPopover](../BbSelectPopover.md) — search is built in, see `disable-writing`. |
241
- | `BbToggle` / `BbToggleGroup` | It is [BbButton](../BbButton.md) with `v-model` (+ `true-value` / `false-value`), which owns `aria-pressed` and the active class. |
242
- | `TagChip` / colored, read-only tag chip | It is [BbBadge](../BbBadge.md) — [BbTag](../BbTag.md) is a tags-_input_ only, not a display component. Use `BbBadge` with a registered `soft-*` variant for a fixed palette, or a one-off `--bg`/`--color` override per row for arbitrary/data-driven colors (BbBadge.md § One-off colors) — not a registered variant per distinct color. |
243
- | `BbTab` (singular) | The consumer API is [BbTabs](../BbTabs.md) / `BbTabsRoot`. `BbTab` appears only as the **v2 "before" side** of the migration diffs. |
244
- | `BbCard` | Does not exist — a card is a `<div>` with tokens. See [design-language](./design-language.md). |
245
- | `BbList` / `BbListItem` / `BbDescriptionList` | Do not exist — a list row is an `<li>` with tokens, and [BbBaseButton](../BbBaseButton.md) makes the whole row navigable. See the note under Data Display. |
246
- | `useLiveFeed` / any polling or cross-tab feed helper | Does not exist, and neither does a cross-tab helper — `useBroadcastChannelInstance` was removed (cross-tab messaging is not a UI concern). Use `useBroadcastChannel` from `@vueuse/core`; the polling and the merge are yours. |
247
- | `BbStepper` / `BbWizard` | Does not exist as a component; the wizard-form recipe composes one. |
248
- | `BbSkeleton` (standalone) | Only `BbTable` ships skeletons, via the column `skeleton` field. |
249
- | `BbCommand` / command palette | Does not exist. Compose `BbDialog` + `BbSelectPopover`. |
250
- | `BbCalendar` (month / scheduling view) | Does not exist. [BbDatePicker](../BbDatePicker.md) is a picker, not a calendar surface. |
251
- | `BbChart` / `BbGraph`, `BbCarousel`, `BbTimeline`, `BbDivider` | Do not exist. No charting, carousel or timeline primitive ships here. |
234
+ | Assumed component or composable | Reality |
235
+ | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
236
+ | `BbColumnManager` — show/hide/reorder columns | Does not exist as a component. Order is `v-model:order` on `BbTable` (an array of keys any panel can write; `reorderable` adds the built-in header drag + keyboard handle); visibility is `hidden: true` on the column definition (the column stays declared and keeps its slot in the order; header slots get a `hideColumn()` that fires `hide:column`). Compose the panel from `BbPopover` + `BbCheckbox`. |
237
+ | `BbKanban` / drag-and-drop board | Does not exist. There is no reusable DnD primitive in the library (`BbTable`'s header drag is internal to the table) — compose the board from `BbTable` (a table/list view of the same data) or hand-roll the drag-and-drop interaction yourself. |
238
+ | `BbModal` | It is [BbDialog](../BbDialog.md). |
239
+ | `BbDrawer` / `BbSheet` | It is [BbOffCanvas](../BbOffCanvas.md). |
240
+ | `BbCombobox` / `BbAutocomplete` | It is [BbSelect](../BbSelect.md) / [BbSelectPopover](../BbSelectPopover.md) — search is built in, see `disable-writing`. |
241
+ | `BbToggle` / `BbToggleGroup` | It is [BbButton](../BbButton.md) with `v-model` (+ `true-value` / `false-value`), which owns `aria-pressed` and the active class. |
242
+ | `TagChip` / colored, read-only tag chip | It is [BbBadge](../BbBadge.md) — [BbTag](../BbTag.md) is a tags-_input_ only, not a display component. Use `BbBadge` with a registered `soft-*` variant for a fixed palette, or a one-off `--bg`/`--color` override per row for arbitrary/data-driven colors (BbBadge.md § One-off colors) — not a registered variant per distinct color. |
243
+ | `BbTab` (singular) | The consumer API is [BbTabs](../BbTabs.md) / `BbTabsRoot`. `BbTab` appears only as the **v2 "before" side** of the migration diffs. |
244
+ | `BbCard` | Does not exist — a card is a `<div>` with tokens. See [design-language](./design-language.md). |
245
+ | `BbList` / `BbListItem` / `BbDescriptionList` | Do not exist — a list row is an `<li>` with tokens, and [BbBaseButton](../BbBaseButton.md) makes the whole row navigable. See the note under Data Display. |
246
+ | `useLiveFeed` / any polling or cross-tab feed helper | Does not exist, and neither does a cross-tab helper — `useBroadcastChannelInstance` was removed (cross-tab messaging is not a UI concern). Use `useBroadcastChannel` from `@vueuse/core`; the polling and the merge are yours. |
247
+ | `BbStepper` / `BbWizard` | Does not exist as a component; the [wizard-form recipe](../recipes/inertia/wizard-form.md) (also under `vue/`, `nuxt/`) composes one. |
248
+ | `BbSkeleton` (standalone) | Only `BbTable` ships skeletons, via the column `skeleton` field. |
249
+ | `BbCommand` / command palette | Does not exist. Compose `BbDialog` + `BbSelectPopover` — do not hand-roll the search: the [command-palette recipe](../recipes/inertia/command-palette.md) (also under `vue/`, `nuxt/`) already wires debouncing, race protection, grouping and keyboard nav. |
250
+ | `BbCalendar` (month / scheduling view) | Does not exist. [BbDatePicker](../BbDatePicker.md) is a picker, not a calendar surface. |
251
+ | `BbDataTable` / `BbDataGrid` / data grid | It is [BbTable](../BbTable.md) — sorting, selection, column order/resize, virtual scrolling and inline edit all live there. |
252
+ | `filter` / `filter-method` prop on BbTable, column filters | No `filter` prop, and its absence is not a missing feature. A filter is an input to the FETCH: put your filter state in `dependencies` and the provider refetches (debounced, value-hashed, races discarded). Per-column filter UI goes in `#header:<key>:append`, which keeps the built-in sort button. A whole filtered listing is the records-workspace recipe. Client-side over a plain array, use a computed: `:items="filteredItems"`. See [BbTable](../BbTable.md) § Filtering. |
253
+ | `BbSnackbar` / `BbNotification` / flash message | It is [BbToast](../BbToast.md), raised through the `useToast` composable. |
254
+ | `BbRichTextEditor` / `BbEditor` / WYSIWYG | Does not exist, and no rich-text or markdown editor ships here. [BbTextarea](../BbTextarea.md) is plain text only; bring your own editor. |
255
+ | `BbEmptyState` / `BbPlaceholder` / no-results panel | Does not exist as a component. [BbTable](../BbTable.md) has a `#no-data` slot; elsewhere an empty state is your own markup with tokens. |
256
+ | `BbChart` / `BbGraph`, `BbCarousel`, `BbTimeline`, `BbDivider` | Do not exist. No charting, carousel or timeline primitive ships here. |
252
257
 
253
258
  App chrome — an app shell, sidebar, page header, selection dock — is
254
259
  deliberately **not** a component: it is yours to own. The per-platform
@@ -14,8 +14,13 @@ components or choose component variants.
14
14
  1. **Never hardcode colors** in custom CSS around components. Use the `--bb-*`
15
15
  custom properties below so your surfaces follow theme and dark mode.
16
16
  2. **Never override component internals** (`.bb-*__*` element classes) from
17
- consumer code. Restyle via tokens, variants, or documented props — the only
18
- exceptions are extension points a component's own doc sanctions.
17
+ consumer code, and never target the reference-only families at all
18
+ (`.bb-base-*`, `.bb-common-*`, listbox internals) — they are the CSS twin of
19
+ the `BbBase*` / `Common*` import boundary and they get restructured without
20
+ a rename table. Restyle via tokens, variants, or documented props. A
21
+ component's **root** class (`.bb-table`, `.bb-badge`) is a legitimate target
22
+ for setting that component's own tokens — see "How to style around the
23
+ library" below.
19
24
  3. **One primary action per view region.** Everything else is `secondary`,
20
25
  `outline`, or `ghost`.
21
26
  4. **`destructive` is only for irreversible actions**, and destructive flows
@@ -25,23 +30,67 @@ components or choose component variants.
25
30
 
26
31
  ## How to style around the library
27
32
 
28
- Three lanes, in order of preference:
33
+ Four lanes, in order of preference. Do not skip to a later lane because an
34
+ earlier one has no obvious knob — check the lane first.
29
35
 
30
36
  1. **Component look → props.** Pick a variant, `size`, `compact`. Never write
31
37
  CSS to make a component look different.
32
38
  2. **Page layout & spacing → Tailwind utilities** (or your project's
33
39
  equivalent) on your own wrapper elements — `flex items-end gap-3`,
34
40
  `grid gap-6`, `max-w-3xl`. Components size themselves; you only place them.
35
- 3. **Genuinely custom visuals → `--bb-*` tokens.** The few CSS rules that
41
+ 3. **Retuning one component → its own component tokens.** Every component
42
+ declares unprefixed `--*` tokens: cell height, padding, badge fill, tab
43
+ strip height. **Set the token on the element that declares it** — that is
44
+ usually the component's root class, and an inline style on the component
45
+ then works too:
46
+
47
+ ```css
48
+ .bb-table {
49
+ --cell-h: 32px;
50
+ --padding-x: 8px;
51
+ }
52
+ ```
53
+
54
+ ```vue
55
+ <BbTree style="--indent: 14px" />
56
+ ```
57
+
58
+ ⚠ **silent** — a component made of several elements declares some tokens on
59
+ an inner one, and a value you set further out is _masked_, not inherited:
60
+ the inner element's own declaration wins. `BbTabs` keeps `--list-h` on
61
+ `.bb-tabs-list`, so `<BbTabs style="--list-h: 40px">` does nothing and
62
+ `.bb-tabs-list { --list-h: 40px }` is the working form. Check which selector
63
+ declares the token before you set it.
64
+
65
+ 4. **Genuinely custom visuals → `--bb-*` tokens.** The few CSS rules that
36
66
  remain (a custom panel, a status dot) use theme tokens so they follow
37
67
  theming and dark mode automatically.
38
68
 
39
- Treat everything inside a component as private API. Where a component is
40
- designed to be extended visually, its own doc names the extension point —
41
- [BbTree](../BbTree.md) has you draw connector lines in consumer CSS anchored
42
- to its `--indent`/`--gap` tokens; [BbTabs](../BbTabs.md) exposes `--list-h`.
43
- Read the per-component doc before writing any CSS that mentions a `.bb-`
44
- class.
69
+ **Finding a component's tokens.** They are not in the `--bb-*` inventory below
70
+ — that tier is the brand knobs only, and it is deliberately small. Component
71
+ tokens live in the component's own CSS, on whichever element consumes them.
72
+ Read `dist/ai/source/<Name>.md`, which carries that CSS verbatim, and grep it
73
+ for the declaration rather than assuming the root. Some components also call
74
+ theirs out in the contract, as [BbTree](../BbTree.md) does for the
75
+ `--indent`/`--gap` pair you anchor connector lines to.
76
+
77
+ **A missing `--bb-*` token is not a gap in the library.** If you want a shorter
78
+ table row and there is no `--bb-table-row-h`, that is lane 3 telling you to set
79
+ `--cell-h` on `.bb-table`, not an invitation to write element-class overrides.
80
+ The full rules, including how per-size lookup tables interact with specificity,
81
+ are in [design-tokens.md](./design-tokens.md) § "Overriding one from a consumer
82
+ app".
83
+
84
+ **A utility class on a component that does nothing is a layer problem, not a
85
+ token problem.** Tailwind v4 puts its utilities in `@layer utilities`, and the
86
+ cascade ranks any unlayered rule above any layered one regardless of
87
+ specificity — so `<BbButton class="px-6">` loses to the library's own padding
88
+ until the library is layered too. That is a build-plugin setting, not CSS:
89
+ `cssLayers: TAILWIND_V4_LAYERS` (exported from `bitboss-ui/vite`) places the
90
+ library after Preflight and before the utilities, and the utility wins. Opt-in,
91
+ and the whole story is in
92
+ [installation-and-plugin-setup](./installation-and-plugin-setup.md) § Cascade
93
+ layers.
45
94
 
46
95
  ## Token Vocabulary
47
96
 
@@ -51,6 +100,13 @@ that change and everything derived follows. The full architecture (tiers,
51
100
  naming rules, the emphasis ladder) lives in
52
101
  [design-tokens.md](./design-tokens.md); this is the consumer summary.
53
102
 
103
+ **This list is closed.** There is no Tailwind-style numeric ramp — no
104
+ `--bb-primary-800`, no `--bb-gray-100`. Emphasis comes from the derived tokens
105
+ below (`--bb-muted`, `--bb-text-muted`, `--bb-surface-hover`) and from
106
+ `color-mix()` against a knob, not from a shade number. A `--bb-*` name that is
107
+ not in these tables resolves to nothing and fails silently, so a rule built on
108
+ one renders with no value at all rather than erroring.
109
+
54
110
  ### Surfaces & structure
55
111
 
56
112
  | Token | Meaning |
@@ -106,9 +162,11 @@ so themes with loud brand colors keep gray chrome.
106
162
 
107
163
  Input-family tokens also exist (`--bb-input-h`, `--bb-input-px`,
108
164
  `--bb-label-fs`…) — see design-tokens.md. Component-specific tokens are
109
- **unprefixed** and defined on each component's root class (e.g. BbTabs'
110
- `--list-h`); override them per instance or at a scope to retune a whole area
111
- coherently instead of styling individual components.
165
+ **unprefixed** and defined on the element that consumes them, usually the
166
+ component's root class (e.g. BbTree's `--indent`). Override them per instance
167
+ or at a scope to retune a whole area coherently instead of styling individual
168
+ components, and set them on the declaring element — see lane 3 above for the
169
+ masking trap.
112
170
 
113
171
  ## Dark Mode
114
172
 
@@ -126,13 +184,14 @@ To crossfade instead of switching instantly, add `theme-animated` to the
126
184
 
127
185
  Variant names are typed per family. Built-in sets:
128
186
 
129
- | Family | Built-in variants |
130
- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------- |
131
- | `BbButton` / `BbDropdownButton` | `primary` `secondary` `outline` `ghost` `destructive` `link` (+ `none` unstyled escape hatch) |
132
- | `BbBadge` | `primary` `secondary` `outline` `ghost` `destructive` |
133
- | `BbAlert` | `primary` `outline` `destructive` `warning` |
134
- | `BbToast` / `useToast` | `default` `success` `info` `warning` `destructive` |
135
- | `BbConfirm` / `useConfirm`, dropdown items, tooltips | `default` `destructive` |
187
+ | Family | Built-in variants |
188
+ | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
189
+ | `BbButton` / `BbDropdownButton` | `primary` `secondary` `outline` `ghost` `destructive` `link` (+ `none` unstyled escape hatch) |
190
+ | `BbBadge` | `primary` `secondary` `outline` `ghost` `destructive` |
191
+ | `BbAlert` | `primary` `outline` `destructive` `warning` |
192
+ | `BbToast` / `useToast` | `default` `success` `info` `warning` `destructive` |
193
+ | `BbConfirm` / `useConfirm`, dropdown items, tooltips | `default` `destructive` |
194
+ | Text-like form controls (`BbTextInput`, `BbTextarea`, `BbNumberInput`, `BbSelect`, `BbTag`, date/time/color inputs) | `outline` (default) `secondary` `ghost` (+ `none` structure-only) — same names, same tokens as the button; `ghost` draws no border in any state, errors show through icon + message |
136
195
 
137
196
  What the shared names mean (shadcn-style vocabulary):
138
197
 
@@ -148,13 +207,84 @@ What the shared names mean (shadcn-style vocabulary):
148
207
 
149
208
  Projects can register extra variants through the Vite/Nuxt plugin
150
209
  (`buttonVariants`, `badgeVariants`, `alertVariants`, `confirmVariants`,
151
- `dropdownItemVariants`, `tooltipVariants`, `toastVariants` options).
210
+ `dropdownItemVariants`, `tooltipVariants`, `toastVariants`, `inputVariants`
211
+ options).
152
212
  Registered names become typed values, but the library ships no CSS for them —
153
213
  the project styles `.bb-button--<name>` (etc.) itself, following the state
154
214
  recipe in [design-tokens.md](./design-tokens.md).
155
215
  Never invent a variant name that is not built-in or registered in the
156
216
  project's plugin config — check `vite.config.*` / `nuxt.config.*` first.
157
217
 
218
+ ### Re-skinning a variant the library already ships
219
+
220
+ Registering a variant covers a name we do **not** ship. Changing how an
221
+ existing one looks is a different job, and the first question is not "which
222
+ CSS" but **how far the change should reach**:
223
+
224
+ 1. **The colour itself is wrong for this brand** (primary is blue, not black) →
225
+ override the **theme knobs**, not any component. `--bb-primary` and its
226
+ family feed every surface, and every derived value and dark-mode variant
227
+ follows. This is the theme builder's path and the only one that stays
228
+ coherent.
229
+ 2. **One area should look different** (a toolbar, a marketing header) →
230
+ re-point the same knobs **on a scope**. Custom properties inherit, so this
231
+ reaches every component in the subtree, whatever shape its variant block
232
+ has:
233
+
234
+ ```css
235
+ .toolbar {
236
+ --bb-primary: #2563eb;
237
+ --bb-primary-hover: #1d4ed8;
238
+ --bb-primary-fg: #fff;
239
+ }
240
+ ```
241
+
242
+ Prefer this to per-component rules. It cannot desynchronise states, and it
243
+ survives dark mode.
244
+
245
+ 3. **One component's take on one variant is wrong** (badges too loud, buttons
246
+ fine) → you have to read that component's variant block first, because
247
+ **they are not built the same way**:
248
+
249
+ | Family | Its variant block sets | So a re-skin is… |
250
+ | ---------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
251
+ | `BbBadge`, `BbTooltip` | component tokens (`--bg`, `--color` / `--fg`, `--border-color`, `--ring`) | re-point those tokens |
252
+ | `BbButton` | CSS properties directly (`background-color`, `border-color`, `color`) | **no token to re-point** — scope the knobs (2), or restate _every_ state |
253
+ | `BbAlert` | both (`--main-color` / `--muted-color`, plus `background-color`) | read the block; half of it is tokens |
254
+ | `BbToast` | only `--icon-color` — the variant tints the icon, nothing else | there is no fill to re-skin |
255
+
256
+ Read the block in `dist/ai/source/<Name>.md` before writing the rule. Assuming
257
+ the badge recipe works on a button is the single most common way this goes
258
+ wrong: `.bb-button--primary { --bg: … }` is silently inert, because the button
259
+ never reads a `--bg`.
260
+
261
+ **Three traps, all of which fail quietly.**
262
+
263
+ **1. A variant is not one declaration.** `primary` on a button sets the rest
264
+ state, `:hover`, `[aria-pressed]`, the `:focus-visible` ring, _and_ an adjacent
265
+ group border. Override `background-color` alone and the old brand survives in
266
+ hover and focus — visible only on interaction, which is why this one reaches
267
+ production. Overriding the knobs (1 or 2) changes every state at once; that is
268
+ the argument for doing it there.
269
+
270
+ **2. Variant blocks are two classes deep.** They are nested under the root, so
271
+ they compile to `.bb-button.bb-button--primary` — more specific than the
272
+ single-class rule you are about to write:
273
+
274
+ ```css
275
+ .bb-button--primary {
276
+ background-color: red;
277
+ } /* 0,1,0 — loses, renders nothing */
278
+ .bb-button.bb-button--primary {
279
+ background-color: red;
280
+ } /* 0,2,0 — wins */
281
+ ```
282
+
283
+ **3. Override the knob, not the derived value.** Values built with
284
+ `color-mix()` off a knob re-derive per theme. Replacing the derived value
285
+ hardcodes it for both schemes, and the bug surfaces as "dark mode looks wrong"
286
+ months later.
287
+
158
288
  ## Using color
159
289
 
160
290
  **Reach for color more than the monochrome default suggests — but make it mean
@@ -295,12 +295,34 @@ Rules:
295
295
  SelectPopover to read) keep a distinctive name per rule 3.
296
296
 
297
297
  Component tokens are still real affordances: a consumer can retune one
298
- instance (`<BbTabs style="--list-h: 40px">`) or a whole page scope. They are
298
+ instance (`<BbTree style="--indent: 14px">`) or a whole page scope. They are
299
299
  just not global namespace.
300
300
 
301
301
  ### Overriding one from a consumer app
302
302
 
303
- Three rules, in order of how often they are broken.
303
+ Four rules, in order of how often they are broken.
304
+
305
+ **0. Set it on the element that declares it.** Sealing (rule 2 above) is what
306
+ makes an inline style on the component root work — and it is also what makes
307
+ the same style _silently do nothing_ when the token lives on an inner element.
308
+ The inner declaration is not overridden by an inherited value; it replaces it.
309
+ `BbTabs` declares `--list-h` on `.bb-tabs-list`, not on `.bb-tabs`:
310
+
311
+ ```css
312
+ /* works — the list is the element that declares it */
313
+ .bb-tabs-list {
314
+ --list-h: 40px;
315
+ }
316
+ ```
317
+
318
+ ```vue
319
+ <!-- does nothing — .bb-tabs-list re-declares --list-h and masks this -->
320
+ <BbTabs style="--list-h: 40px" />
321
+ ```
322
+
323
+ Grep the component's CSS in `dist/ai/source/<Name>.md` for the declaration
324
+ before choosing a selector. A masked token produces no warning and no visual
325
+ change.
304
326
 
305
327
  **1. Override only what you actually render.** Several components expose a
306
328
  per-size lookup table (`--icon-size-xs … --icon-size-2xl`, `--h-xs … --h-2xl`)
@@ -334,7 +356,10 @@ silently does nothing on every `size="sm"` one — the confusing half-failure th
334
356
  sends people to the shotgun in rule 1. The per-size entry has no competing
335
357
  declaration, so it always wins. (A scope selector of equal-or-greater
336
358
  specificity also works, but you have to keep winning that race; overriding the
337
- entry does not.)
359
+ entry does not. If your app is on Tailwind v4 or otherwise layered, the race
360
+ has a structural answer: `cssLayers` in the build plugin puts the library in a
361
+ layer below your utilities, and a utility then wins without any specificity at
362
+ all — see the install guide § Cascade layers.)
338
363
 
339
364
  **3. Scope it to the component, never `:root`.** Short names are deliberately
340
365
  reused across components — `--icon-size` is read by `BbButton`, `BbBadge` and
@@ -656,7 +681,7 @@ like it would replace the seal rule structurally, but it breaks the system:
656
681
 
657
682
  - `inherits: false` stops the root→descendant flow _inside_ a component
658
683
  (declare on `.bb-base-switch`, consume on `__icon` — that is inheritance),
659
- which is also what makes `style="--list-h: 40px"` overrides work. CSS
684
+ which is also what makes `style="--indent: 14px"` overrides work. CSS
660
685
  cannot express "inherit within my subtree but not into nested components".
661
686
  - A registered property always has a value, so `var(--max-h, none)`
662
687
  fallbacks never fire again — our sealing pattern dies.