bitboss-ui 3.0.0-beta.4 → 3.0.0-beta.8

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 (42) hide show
  1. package/README.md +36 -29
  2. package/dist/ai/BbBadge.md +10 -5
  3. package/dist/ai/BbSelect.md +8 -0
  4. package/dist/ai/BbTable.md +257 -51
  5. package/dist/ai/changelog.json +86 -2
  6. package/dist/ai/components.json +25 -15
  7. package/dist/ai/guides/design-language.md +14 -14
  8. package/dist/ai/guides/design-tokens.md +25 -18
  9. package/dist/ai/guides/installation-and-plugin-setup.md +1 -1
  10. package/dist/ai/guides/migration/components/bb-dialog.md +35 -29
  11. package/dist/ai/guides/migration/components/bb-table.md +406 -28
  12. package/dist/ai/guides/migration/v2-to-v3.md +175 -75
  13. package/dist/ai/index.md +1 -1
  14. package/dist/ai/recipes/inertia/layout-scaffold.md +5 -1
  15. package/dist/ai/recipes/inertia/ownership-atlas.md +41 -20
  16. package/dist/ai/recipes/inertia/records-workspace.md +26 -7
  17. package/dist/ai/recipes/nuxt/layout-scaffold.md +5 -1
  18. package/dist/ai/recipes/nuxt/records-workspace.md +26 -7
  19. package/dist/ai/recipes/vue/layout-scaffold.md +5 -1
  20. package/dist/ai/recipes/vue/records-workspace.md +26 -7
  21. package/dist/ai/source/BbBaseInputContainer.md +26 -0
  22. package/dist/ai/source/BbBreadcrumbs.md +31 -0
  23. package/dist/ai/source/BbDialog.md +15 -9
  24. package/dist/ai/source/BbOffCanvas.md +17 -11
  25. package/dist/ai/source/BbProgress.md +2 -2
  26. package/dist/ai/source/BbTable.md +837 -787
  27. package/dist/ai/source/CommonInputWrapper.md +4 -0
  28. package/dist/components/BbPagination/BbPagination.vue.d.ts +1 -1
  29. package/dist/components/BbTable/BbTable.vue.d.ts +5 -2
  30. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +439 -484
  31. package/dist/components/BbTable/types.d.ts +83 -21
  32. package/dist/components/BbTable/utils.d.ts +18 -3
  33. package/dist/components/BbTable/utils.js +36 -28
  34. package/dist/index.d.ts +5 -1
  35. package/dist/index.js +68 -67
  36. package/dist/llms-full.txt +1065 -265
  37. package/dist/llms-medium.txt +15 -15
  38. package/dist/llms.txt +1 -1
  39. package/dist/styles.css +1 -1
  40. package/llms.txt +1 -1
  41. package/package.json +1 -1
  42. package/dist/assets/svgs/sort.svg_raw.js +0 -4
@@ -314,19 +314,30 @@ to the rename map and to every grep below — see
314
314
  | `--bb-hint`, `--bb-icon-color`, `--bb-placeholder`, `--bb-prefix-color`, `--bb-muted-color` | `--bb-text-muted` |
315
315
  | `--bb-input-color`, `--bb-input-bg` | `--bb-text`, `--bb-panel` |
316
316
  | `--bb-input-font-size`, `--bb-input-mobile-font-size` | `--bb-input-fs`, `--bb-input-fs-mobile` |
317
+ | `--bb-label-size` | `--bb-label-fs` |
318
+ | `--bb-label-compact-spacing-y` | `--bb-label-spacing-y-compact` |
319
+ | `--bb-input-compact-h`, `--bb-input-compact-py` | `--bb-input-h-compact`, `--bb-input-py-compact` — the `compact` qualifier moved from infix to suffix |
320
+ | `--bb-input-compact-px`, `--bb-input-compact-prefix-w`, `--bb-input-compact-floating-py`, `--bb-input-prefix`, `--bb-input-prefix-px` | **deleted, no counterpart.** v3's compact mode re-points only height and vertical padding (the two tokens in the row above); horizontal padding and the prefix column keep their normal-size values, and the prefix's own font-size/padding knobs are gone. |
317
321
  | `--bb-overlay-color` + `--bb-overlay-opacity` | `--bb-overlay` (one color with alpha) |
318
322
  | `--bb-overlay-z-index` | `--bb-z-overlay` |
319
323
  | _(no v2 equivalent)_ | `--bb-ease` — new in v3. v2 hard-coded seven different `cubic-bezier()` curves and exposed no easing token, so there is nothing to rename; set `--bb-ease` once if you want a house curve. |
320
- | `--bb-dialog-px`, `--bb-dialog-py` | `--bb-panel-p` — **one value on both axes**, for dialogs and off-canvas alike. If your v2 px ≠ py, see "Asymmetric surface padding" below. |
324
+ | `--bb-dialog-px`, `--bb-dialog-py` | the local pair `--px` / `--py` on `.bb-dialog` / `.bb-offcanvas` — a direct map, set on the component's class (never `:root`). Both branch off `--bb-panel-p`, so if your v2 px = py, set `--bb-panel-p` alone. See "Asymmetric surface padding" below. |
321
325
  | `--bb-dialog-close` | **delete it** — do not map it onto `.bb-close-button { --size }`; v3's default already reproduces the v2 control. See below. |
322
326
  | `--bb-dialog-title-size` | the local `--dialog-title-fs` on `.bb-dialog` / `.bb-offcanvas` (default `calc(var(--bb-fs) + 2px)`) |
323
327
  | `--bb-dialog-title-weight` | **no knob** — the title is hardcoded `font-weight: 500`. Override with a CSS rule, see below. |
324
328
  | `--bb-button-h`, `--bb-button-icon`, `--bb-checkbox-*`, `--bb-radio-*`, `--bb-switch-*`, `--bb-rating-*`, `--bb-slider-*`, `--bb-table-cell-h`, `--bb-select-option-*` | **no longer global.** Component dimensions hang off `--bb-control-h`, `--bb-fs`, `--bb-radius`; anything finer is an unprefixed local on the component's own class — with one exception: `--bb-select-option-px/-py` have **no** v3 local, the listbox option's inline padding is a literal (`.bb-listbox__option { padding-inline: … }` in your own CSS). |
325
329
 
326
- **`--bb-input-*` and `--bb-label-*` are NOT in the row above.** They are still
327
- global v3 theme tokens, declared in `variables.css` and read across the whole
328
- form family — do not delete them. Several changed their default value; the
329
- drift table below lists which.
330
+ **Most `--bb-input-*` and `--bb-label-*` tokens survive — but only these
331
+ exact names:** `--bb-input-h`, `--bb-input-px`, `--bb-input-py`,
332
+ `--bb-input-spacing`, `--bb-input-icon`, `--bb-input-prefix-w`,
333
+ `--bb-label-spacing-x`, `--bb-label-spacing-y`, `--bb-label-weight`,
334
+ `--bb-leading`. Those are still global v3 theme tokens, declared in
335
+ `variables.css` and read across the whole form family — do not delete them
336
+ (several changed default value; the drift table below lists which). Everything
337
+ else in the two families either renamed (`--bb-label-size` → `--bb-label-fs`,
338
+ the compact pair — rows above) or is deleted outright, so a declaration that
339
+ is not in this list and not in the rename map is inert: it looks correct and
340
+ does nothing. §8's retired-token grep catches every deleted name.
330
341
 
331
342
  **When a v2 global becomes a component token, resize only what you use.** That
332
343
  last row is the trap: a v2 global like `--bb-button-icon` had ONE value for
@@ -347,50 +358,59 @@ doctrine in `design-tokens.md` § "Overriding one from a consumer app". And chec
347
358
  first whether the v2 value was a real customisation: if it merely restated v2's
348
359
  default, the migration is to **delete it** and inherit the v3 scale.
349
360
 
350
- **Asymmetric surface padding.** `--bb-panel-p` is a single value used on both
351
- axes. If your v2 theme set `--bb-dialog-px` ≠ `--bb-dialog-py`, set
352
- `--bb-panel-p` to the **horizontal** value (it also positions the close button)
353
- and override the vertical with a normal `padding` rule on the part classes.
354
- There is no `--bb-panel-px` / `--bb-panel-py` pair.
361
+ **Asymmetric surface padding.** v3 splits the axes with the local pair
362
+ `--px` / `--py`, declared on both surface roots (`.bb-dialog`,
363
+ `.bb-offcanvas`) and each branching off `var(--bb-panel-p)`. Like every
364
+ unprefixed component token, you override them _on the component's class_,
365
+ never `:root`. v2's `--bb-dialog-px` / `--bb-dialog-py` map onto them
366
+ directly:
355
367
 
356
368
  ```css
357
- /* v2: --bb-dialog-px: 24px; header --bb-dialog-py: 10px; body --bb-dialog-py: 20px */
369
+ /* v2: --bb-dialog-px: 24px; --bb-dialog-py: 10px */
358
370
  .my-dialog {
359
- --bb-panel-p: 24px; /* horizontal + close-button inset */
371
+ --px: 24px; /* horizontal padding + the close button's inline inset */
372
+ --py: 10px; /* vertical padding + the close button's block inset */
360
373
  }
374
+ ```
375
+
376
+ Setting only `--bb-panel-p` still moves both axes together (the pair defaults
377
+ to it, resolved on the component root, so a scoped `--bb-panel-p` override
378
+ keeps working too). Per-**part** asymmetry (header `py: 10px`, body
379
+ `py: 20px`) works by scoping the token to the part class — the padding rules
380
+ resolve it on the part elements and the pair inherits:
381
+
382
+ ```css
361
383
  .my-dialog .bb-dialog__header {
362
- padding-block: 10px 5px;
363
- }
364
- .my-dialog .bb-dialog__body .bb-dialog__body-content {
365
- padding-block: 0 20px;
366
- }
367
- .my-dialog .bb-dialog__body:first-child .bb-dialog__body-content {
368
- padding-top: 20px;
384
+ --py: 10px; /* header block padding (bottom is py/2); × inset follows */
369
385
  }
370
- .my-dialog .bb-dialog__footer {
371
- padding-block: 20px;
386
+ .my-dialog .bb-dialog__body-content {
387
+ --py: 20px; /* body bottom padding, and its top in the headerless case */
372
388
  }
373
389
  ```
374
390
 
375
- Use `padding-block`, not the `padding` shorthand, so the horizontal half keeps
376
- tracking `--bb-panel-p`. Specificity: the library rules are
377
- `.bb-dialog .bb-dialog__header` (0,2,0), `.bb-dialog__body-content` (0,1,0) and
378
- `.bb-dialog__body:first-child .bb-dialog__body-content` (0,2,1) — the
379
- three-class app selectors above beat all of them. `BbOffCanvas` is identical
380
- with `.bb-offcanvas__*`.
381
-
382
- **`--px` and `--pt` are not dialog knobs.** `.bb-dialog` declares them, but
383
- nothing in the dialog or off-canvas CSS reads them, and every component that
384
- _does_ read `--px` (`BbButton`, `BbAlert`, `BbTooltip`, `BbBaseSlider`)
385
- re-declares it on its own root class, so an ancestor value never reaches those
386
- either. Setting `--px` on a dialog part is inert — it silently leaves the
387
- horizontal padding at whatever `--bb-panel-p` resolves to.
391
+ Derived spacings scale with it — the header's bottom padding is `py / 2` and
392
+ the close button's block inset tracks the header's `py`. If you need exact
393
+ literals instead of those ratios, a `padding-block` rule on the part class
394
+ still works (use `padding-block`, not the `padding` shorthand, so the
395
+ horizontal half keeps tracking `--px`; the library part selectors are
396
+ at most (0,2,1) specific, so a three-class app selector beats them all).
397
+ `BbOffCanvas` is identical with `.bb-offcanvas__*` and the same `--px` /
398
+ `--py` names.
399
+
400
+ **Scope the pair to the surface, and it stays the surface's.** `--px` is also
401
+ a local on other components (`BbButton`, `BbAlert`, `BbTooltip`,
402
+ `BbBaseSlider`), but each of those re-declares it on its own root class, so a
403
+ dialog-scoped `--px` never leaks into the buttons or alerts inside the dialog
404
+ — and, symmetrically, never set the pair on `:root` or an arbitrary ancestor:
405
+ the surface roots' own defaults mask it there, and you would be reaching into
406
+ every other `--px`-reading component anyway. (`--pt` remains dead — nothing
407
+ reads it.)
388
408
 
389
409
  If only the **vertical rhythm between parts** differs, the local `--dialog-gap`
390
- (default `--bb-panel-p`) already does it without a padding rule: it drives the
391
- gap below the body content and the footer's block padding on `.bb-dialog` and
410
+ (default `--py`) already does it without a padding rule: it drives the gap
411
+ below the body content and the footer's block padding on `.bb-dialog` and
392
412
  `.bb-offcanvas`. It does **not** cover the header's top padding, or the body's
393
- top padding in the headerless case — those still take `--bb-panel-p`.
413
+ top padding in the headerless case — those still take `--py`.
394
414
 
395
415
  **`--bb-dialog-close` maps to nothing — delete it.** In v2 it was the close
396
416
  **icon** width (`12px`) and the button around it added `8px` of padding, so the
@@ -454,15 +474,25 @@ and delete only the declarations that merely restated a **v2** default.
454
474
  **A type that narrowed.** `--bb-ring-opacity` survives by name but v3 registers
455
475
  it with `@property … syntax: '<percentage>'`:
456
476
 
457
- | token | v2 form | v3 form | symptom |
458
- | ------------------- | -------------------------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
459
- | `--bb-ring-opacity` | unitless `0.25` / `0.4`, multiplied by `100%` internally | `25%` / `40%` — a `<percentage>` | ⚠ silent: a unitless override is invalid at computed-value time, so it is discarded and the token falls back to the registered `initial-value: 25%`. v3's own defaults are `15%` light / `40%` dark. |
477
+ | token | v2 form | v3 form | symptom |
478
+ | ------------------- | -------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
479
+ | `--bb-ring-opacity` | unitless `0.25` / `0.4`, multiplied by `100%` internally | `25%` / `40%` — a `<percentage>` | ⚠ silent: a unitless override is invalid at computed-value time, so it is discarded and the token falls back to the registered `initial-value: 15%` — v3's own light default, so a light page silently renders the stock ring, while a dark page lands on `15%` against the shipped `40%`. |
460
480
 
461
481
  Nothing reports this — not `vue-tsc`, not the eslint plugin, not
462
482
  `npx bitboss-ui check` (none of them read CSS), and not the browser console:
463
483
  an invalid registered-property value is not a parse error, so it logs nothing.
464
- Grep your stylesheets for `--bb-ring-opacity` and confirm every value carries a
465
- `%`.
484
+ Grep your stylesheets for `--bb-ring-opacity` and check **both directions of
485
+ every hit**:
486
+
487
+ - every **write** must carry a `%` (`0.15` → `15%`), and
488
+ - every **read** must stop multiplying by `100%`. The v2 idiom for consuming
489
+ the unitless form was exactly that —
490
+ `calc(100% - var(--bb-ring-opacity) * 100%)` — and under v3 it multiplies a
491
+ percentage by a percentage, which is invalid in `calc()`. The invalid math
492
+ takes the whole declaration down (typically a `color-mix()` ring), so the
493
+ failure is _worse_ than the write side: a stale write still produces a ring
494
+ at the fallback strength, a stale read produces **no ring at all**, equally
495
+ silently. Delete the `* 100%`.
466
496
 
467
497
  Nine tokens are `@property`-registered in v3 — the eight color knobs
468
498
  (`syntax: '<color>'`) plus `--bb-ring-opacity`. Each rejects values outside its
@@ -565,9 +595,26 @@ this order:
565
595
  1. **Name is in the table, and your app never styled it** → use the built-in
566
596
  `variant`. This is the only genuinely free conversion.
567
597
  2. **Name is in the table, but your app styled it itself** → v3's built-in now
568
- paints underneath your rules. Decide explicitly: accept v3's look and delete
569
- your CSS, or register the name (`alertVariants: ['warning']`) so your CSS
570
- stays the sole styling source.
598
+ paints underneath your rules, and **registering the name does not stop it**.
599
+ The registries feed the type-checker only: `alertVariants: ['warning']`
600
+ regenerates the typed union and nothing else — no CSS is generated and none
601
+ is suppressed; the built-in `.bb-alert--warning` skin ships unconditionally
602
+ in `styles.css` and still applies, so every property your CSS does not
603
+ explicitly set shows through as the library's. Decide explicitly: accept
604
+ v3's look and delete your CSS, or keep your look — which requires your rules
605
+ to **win the cascade** over the library's. Note the library's skin
606
+ selectors are not uniform: some families compile single-class
607
+ (`.bb-alert--warning`, `.bb-toast-message--*` — (0,1,0)), others
608
+ block-scoped (`.bb-button.bb-button--outline`, `.bb-badge.bb-badge--*`,
609
+ `.bb-indicator.bb-indicator--*`, `.bb-tooltip.bb-tooltip--*` — (0,2,0)).
610
+ The pattern that wins everywhere: author your override at block+modifier
611
+ (`.bb-alert.bb-alert--warning`, or nest the modifier under the block) **and**
612
+ load your sheet after `styles.css` — that outweighs the single-class
613
+ families and ties-then-wins-on-order against the block-scoped ones. Or
614
+ sidestep the collision by renaming to a name not in the table, which moves
615
+ you to case 3, where your CSS genuinely is the sole source. Whichever you pick,
616
+ still register the name: that part is required for the type-check either
617
+ way.
571
618
  3. **Name is not in the table** → register it and keep your CSS. **Do not remap
572
619
  onto the "nearest" built-in.** `error → destructive`, `danger →
573
620
  destructive`, `info → primary` all compile, all look plausible in review,
@@ -575,8 +622,12 @@ destructive`, `info → primary` all compile, all look plausible in review,
575
622
  deliberate redesign decision, never as a mechanical migration step.
576
623
  4. **Name was never styled by anyone** → see the next subsection.
577
624
 
578
- Registering is cheap — one array entry per name — and it is the
579
- behaviour-preserving default. Prefer it whenever you are not sure.
625
+ Registering is cheap — one array entry per name — and for a name **not** in
626
+ the table it is the behaviour-preserving default: the library has no rule for
627
+ an unknown name, so your CSS is the sole styling source. For a name that **is**
628
+ in the table, registering buys the type-check and nothing else — behaviour
629
+ preservation there is a cascade question (case 2 above). When unsure, prefer a
630
+ non-colliding name.
580
631
 
581
632
  ### ⚠ silent — a `theme` value that did nothing in v2 may do something in v3
582
633
 
@@ -837,6 +888,15 @@ is the complete set — a class-by-class diff of the v2 stylesheet against v3
837
888
  leaves nothing else orphaned. All of it is **silent**: a selector on a class
838
889
  nobody emits matches nothing and throws nothing.
839
890
 
891
+ One caution when auditing on your own: absence from `styles.css` answers _"is
892
+ it styled"_, not _"is it emitted"_. Some classes carry no library rule but are
893
+ still rendered as styling hooks for you (`.bb-base-input-container__hint-text`
894
+ is one: its wrapper `__hint-wrap` carries the rules, the inner text node is
895
+ bare) — those are correct targets and correctly
896
+ absent from this table, which maps only classes that moved or died. Before
897
+ filing a class as removed, check the component's markup, not just the
898
+ stylesheet.
899
+
840
900
  **Input chrome**
841
901
 
842
902
  | v2 | v3 |
@@ -881,6 +941,23 @@ v2 had no full-width tab strip, so apps hand-rolled one (commonly a
881
941
  `.bb-tabs--block` / `.bb-tabs-list--block` and gives the triggers equal widths.
882
942
  Delete the hand-rolled modifier rather than porting it.
883
943
 
944
+ **BbTable** — no class was renamed, but the markup under `.bb-table` is no
945
+ longer a `<table>`: it is a CSS grid of `<div>`s carrying ARIA roles. Every
946
+ **element** selector on it (`table`, `thead`, `tbody`, `tfoot`, `caption`,
947
+ `tr`, `th`, `td`, `colgroup`/`col`) matches nothing in v3 — silently. The
948
+ classes all survive; four containers and the roles are the translation:
949
+
950
+ | v2 element | v3 selector |
951
+ | ------------------------------------- | -------------------------------------------------------------------------------------------- |
952
+ | `table` / `thead` / `tbody` / `tfoot` | `.bb-table__table` / `.bb-table__head` / `.bb-table__body` / `.bb-table__foot` |
953
+ | `caption` | `.bb-table-caption` |
954
+ | `tr` / `th` / `td` | `[role=row]` / `[role=columnheader]` / `[role=cell]` (or the `.bb-table-*` row/cell classes) |
955
+ | `colgroup` / `col` | gone — `columns[].width` |
956
+
957
+ Full table, the `.table-flush` idiom rewrite, the `#thead`/`#tbody`/`#tfoot`
958
+ markup contract and the layout drift that comes with the substrate:
959
+ [bb-table.md](./components/bb-table.md#the-table-is-a-css-grid-not-a-table-v3-beta-2026-08-26).
960
+
884
961
  **Everything else**
885
962
 
886
963
  | v2 | v3 |
@@ -914,9 +991,13 @@ The date **input**'s own field tree (`.bb-base-date-picker-input__field`,
914
991
  **Removed with no counterpart:** `.bb-base-dialog--compact`,
915
992
  `.bb-offcanvas--compact`, `.bb-base-textarea--compact` (compact now lives on
916
993
  `.common-input-wrapper--compact`), `.bb-base-select--active` / `--compact` /
917
- `__inner-wrapper` / `__input-container` (the select activator was rebuilt —
918
- nearest hooks are `.bb-base-select__activator`, `__value`, `__display-value`;
919
- `--shown` survives), `.bb-base-rating__option--highlighted` (v3 uses
994
+ `__inner-wrapper` / `__input-container` / `__text-input` (the select activator
995
+ was rebuilt — nearest hooks are `.bb-base-select__activator`, `__value`,
996
+ `__display-value`; `--shown` survives. `__text-input` was v2's combobox typing
997
+ field inside the activator; v3's activator is a `type="button"` overlay with no
998
+ typable field, and typing moved into the panel's
999
+ `.bb-select-popover__search-input` — restyle that instead, knowing it now sits
1000
+ in the popover, not the field), `.bb-base-rating__option--highlighted` (v3 uses
920
1001
  `--filled` / `--empty`), `.bb-progress--horizontal` (`.bb-progress` /
921
1002
  `.bb-progress-bar` are the blocks), `.bb-ratio` (component removed).
922
1003
 
@@ -928,23 +1009,27 @@ nested-table column alignment. They all dropped the `bb-` prefix, and every one
928
1009
  of them is consumed through `var(name, fallback)`, so a v2 name silently
929
1010
  resolves to the fallback instead of failing.
930
1011
 
931
- | v2 | v3 |
932
- | ----------------------------------------------------- | -------------------------------------------------------------------------- |
933
- | `--bb-table-{id}-track-{key}` | `--table-{id}-track-{key}` |
934
- | `--bb-table-offset-start` / `-end` | `--offset-start` / `--offset-end` |
935
- | `--bb-table-offset-internal-start` / `-end` | `--offset-internal-start` / `--offset-internal-end` |
936
- | `--bb-table-fill` | `--fill` |
937
- | `--bb-table-natural-width` | `--natural-width` |
938
- | `--bb-select-popover-width` (on `.bb-select-popover`) | `--w` — and the supported path is now the `width` prop, not a CSS override |
939
- | `--bb-icon-dimensions-w` (on `.bb-icon`) | `--w` |
940
- | `--bb-breadcrumbs-flex-basis` / `-flex-grow` | `--flex-basis` / `--flex-grow` |
941
- | `--bb-picker-preview-color` | `--picker-preview-color` |
1012
+ | v2 | v3 |
1013
+ | ----------------------------------------------------- | ----------------------------------------------------------------------------------------- |
1014
+ | `--bb-table-{id}-track-{key}` | `--table-{id}-track-{key}` |
1015
+ | `--bb-table-offset-start` / `-end` | `--offset-start` / `--offset-end` |
1016
+ | `--bb-table-offset-internal-start` / `-end` | `--offset-internal-start` / `--offset-internal-end` |
1017
+ | `--bb-table-fill` | **removed in 3.0.0** (briefly `--fill` in the betas; the grid substrate has no fill loop) |
1018
+ | `--bb-table-natural-width` | **removed in 3.0.0** (briefly `--natural-width` in the betas) |
1019
+ | `--bb-select-popover-width` (on `.bb-select-popover`) | `--w` — and the supported path is now the `width` prop, not a CSS override |
1020
+ | `--bb-icon-dimensions-w` (on `.bb-icon`) | `--w` |
1021
+ | `--bb-breadcrumbs-flex-basis` / `-flex-grow` | `--flex-basis` / `--flex-grow` |
1022
+ | `--bb-picker-preview-color` | `--picker-preview-color` |
942
1023
 
943
1024
  A regex matching the old shape (`/--bb-table-[\w-]+-track/`) matches nothing in
944
1025
  v3. Search your app for `--bb-table-`, `--bb-select-popover-`,
945
1026
  `--bb-icon-dimensions-`, `--bb-breadcrumbs-` and `--bb-picker-` — none of those
946
1027
  names exist in v3 and all of them fail quietly. `--available-height`,
947
- `--inner-width` and `--overlay-min-height` were dropped outright.
1028
+ `--inner-width`, `--overlay-min-height`, `--bb-table-fill` and
1029
+ `--bb-table-natural-width` were dropped outright — the last two because
1030
+ `BbTable` is a CSS grid in 3.0.0 and the fixed-layout self-correction they fed
1031
+ no longer exists (see
1032
+ [bb-table.md](./components/bb-table.md#--fill-and---natural-width-are-removed-not-renamed)).
948
1033
 
949
1034
  If you re-published track variables by hand to align a deeply nested table,
950
1035
  delete that code: `inherit-column-widths` takes a **table id**
@@ -1013,6 +1098,16 @@ Nothing here fails to compile — these change what existing code does.
1013
1098
  branch's children are now `inert` (out of the Tab order). Tests or CSS
1014
1099
  keyed on the removed roles match nothing — see
1015
1100
  [bb-tree.md](./components/bb-tree.md).
1101
+ 13. **`BbTable` is a CSS grid, not a `<table>`.** Column widths hold their
1102
+ overflow threshold, wrap floor and frozen widths exactly, but leftover
1103
+ space is shared equally between auto columns rather than proportionally
1104
+ to content, and a few table-only tricks stop working (`max-width: 0` for
1105
+ ellipsis collapses the cell). Copying rows into a spreadsheet pastes one
1106
+ value per line, and a printed table no longer repeats its header on each
1107
+ page. `#thead`/`#tbody`/`#tfoot` take `<div role="row">` rows, never
1108
+ `<tr>`. Requires subgrid (Chrome/Edge 117, Firefox 71, Safari 16) — no
1109
+ fallback. Full detail and the selector translation in
1110
+ [bb-table.md](./components/bb-table.md#the-table-is-a-css-grid-not-a-table-v3-beta-2026-08-26).
1016
1111
 
1017
1112
  ## 7. Class overrides → variants
1018
1113
 
@@ -1046,8 +1141,12 @@ screenshot; do not assume. Two consequences before you start:
1046
1141
  `variant="none"`; it is the one value that emits no variant class.
1047
1142
  - **A name that collides with a v3 built-in is the dangerous case, not the easy
1048
1143
  one.** If your app owned the look of `warning` / `destructive` / `secondary`,
1049
- converting to the same-named `variant` hands that look to the library.
1050
- Register the name as a custom variant (§4) to keep your CSS the only source.
1144
+ converting to the same-named `variant` hands that look to the library — and
1145
+ registering the name does **not** take it back: registration is type-only
1146
+ (§4), the built-in skin stays in `styles.css`. Keep your look by winning the
1147
+ cascade over the library's skin rules — author at block+modifier and load
1148
+ after `styles.css` (§4 case 2 has the specificity detail) — or convert to a
1149
+ non-colliding custom name.
1051
1150
 
1052
1151
  ### Finding them
1053
1152
 
@@ -1067,17 +1166,17 @@ rg -n 'class="bb-(button|badge|alert|toast-message|tooltip|indicator)\b' --type
1067
1166
 
1068
1167
  ### Correcting them
1069
1168
 
1070
- | Found | Rewrite as |
1071
- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1072
- | `<BbButton class="bb-button--outline">` | `<BbButton variant="outline">` (same for `secondary`, `ghost`, `destructive`, `link`) |
1073
- | `<BbAlert class="bb-alert--warning">` / v2 `theme="warning"` | `<BbAlert variant="warning">` |
1074
- | `class="bb-badge bb-badge--secondary"` on a `<span>` | `<BbBadge variant="secondary">` — the real component, not markup cosplay |
1075
- | `bb-toast-message--success` styling / v2 `theme` on toasts | `toast({ variant: 'success' })` |
1076
- | `bb-tooltip--destructive` | `<BbTooltip variant="destructive">` / `v-bb-tooltip="{ text, variant: 'destructive' }"` |
1077
- | A **custom** modifier (`bb-button--brand`, any name not in section 4's table) | Register it: `buttonVariants: ['brand']` in the plugin, keep your CSS, pass `variant="brand"`. |
1078
- | A modifier your app **styled itself**, whatever its name | Decide before you convert: accept v3's built-in look and delete your CSS, or register the name (§4) so your CSS stays the sole styling source. Converting silently does the former. |
1079
- | A **state** modifier that stacks on a variant (`bb-button--pressed` alongside `bb-button--outline`) | Not convertible — `variant` holds exactly one value. Keep it a class and rename it out of `bb-*` (`app-button--pressed`). If it marks a real toggle, `v-model` on `BbButton` owns the state: it sets `aria-pressed` and emits `bb-button--active`, which the library deliberately leaves unstyled for you. |
1080
- | A class that overrides _layout_, not look (widths, margins) | Leave it — classes remain the right tool for layout; variants own _appearance semantics_. |
1169
+ | Found | Rewrite as |
1170
+ | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1171
+ | `<BbButton class="bb-button--outline">` | `<BbButton variant="outline">` (same for `secondary`, `ghost`, `destructive`, `link`) |
1172
+ | `<BbAlert class="bb-alert--warning">` / v2 `theme="warning"` | `<BbAlert variant="warning">` |
1173
+ | `class="bb-badge bb-badge--secondary"` on a `<span>` | `<BbBadge variant="secondary">` — the real component, not markup cosplay |
1174
+ | `bb-toast-message--success` styling / v2 `theme` on toasts | `toast({ variant: 'success' })` |
1175
+ | `bb-tooltip--destructive` | `<BbTooltip variant="destructive">` / `v-bb-tooltip="{ text, variant: 'destructive' }"` |
1176
+ | A **custom** modifier (`bb-button--brand`, any name not in section 4's table) | Register it: `buttonVariants: ['brand']` in the plugin, keep your CSS, pass `variant="brand"`. |
1177
+ | A modifier your app **styled itself**, whatever its name | Decide before you convert: accept v3's built-in look and delete your CSS, or keep yours. Keeping it is free for a non-built-in name (register it, done — there is no library rule to fight); for a built-in name registering only satisfies the type-check, and your rules must also win the cascade over the library's skin (§4, case 2). Converting silently does the former. |
1178
+ | A **state** modifier that stacks on a variant (`bb-button--pressed` alongside `bb-button--outline`) | Not convertible — `variant` holds exactly one value. Keep it a class and rename it out of `bb-*` (`app-button--pressed`). If it marks a real toggle, `v-model` on `BbButton` owns the state: it sets `aria-pressed` and emits `bb-button--active`, which the library deliberately leaves unstyled for you. |
1179
+ | A class that overrides _layout_, not look (widths, margins) | Leave it — classes remain the right tool for layout; variants own _appearance semantics_. |
1081
1180
 
1082
1181
  Rule of thumb: if the class name encodes a **meaning** (state, emphasis,
1083
1182
  severity), it should be a variant; if it encodes **geometry**, it stays a
@@ -1156,6 +1255,7 @@ rg -n -- '--bb-(contrasting|hint|icon-color|primary-(base|dark)|panel-(light|dar
1156
1255
  rg -n -- '--bb-ring-opacity' # survives by name; every value must now carry a % (section 3)
1157
1256
  rg -n 'bb-base-dialog|bb-tab__|bb-tab--|bb-cr-container|bb-common-input-outer-container|bb-common-input-inner-container|bb-base-input-container__input|bb-base-(checkbox|radio|switch)-container|bb-tree-row|bb-tree-main-content|bb-confirm__no|bb-dropdown__wrapper|bb-offcanvas__close|bb-popover__close|bb-ratio' # renamed or deleted CSS blocks — nothing in v3 emits any of these (§5.6)
1158
1257
  rg -n 'bb-color-palette__' # → .bb-base-color-palette__*; only __popover survives under the old name
1258
+ rg -n '\b(table|thead|tbody|tfoot|caption|colgroup|col|tr|th|td)\b' --type css --type vue # element selectors aimed at BbTable (`.bb-table table`, `:deep(> table > tbody …)`, a global `td { padding }`, `.prose table`) — BbTable is a div grid in v3, none of them reach it; translate per bb-table.md § Selector translation. Expect noise from `<table>` markup of your own and from words in comments — judge each hit
1159
1259
  rg -n 'bb-tabs--' # v3's own modifiers are --horizontal --vertical --block --compact --disabled; anything else is app-minted, and a hand-rolled full-width tab list is now <BbTabs block>
1160
1260
  ```
1161
1261
 
package/dist/ai/index.md CHANGED
@@ -241,7 +241,7 @@ Do not use these components directly in consumer code unless the user explicitly
241
241
  - [Migration v2→v3: BbSlider](./guides/migration/components/bb-slider.md) - thumbTranslate inverted to disableThumbTranslate; string model values and color removed.
242
242
  - [Migration v2→v3: BbSmoothHeight](./guides/migration/components/bb-smooth-height.md) - The `tag` prop is narrowed to `div | span`, and the inner measuring element now mirrors it instead of always being a `<div>`.
243
243
  - [Migration v2→v3: BbSpinner](./guides/migration/components/bb-spinner.md) - The color prop is removed; spinners follow currentColor.
244
- - [Migration v2→v3: BbTable](./guides/migration/components/bb-table.md) - allowSelectAll inverted to disableSelectAll; loading no longer blanks populated tables and now inerts rows; #loading is first-load-only; #no-data fills a full-width table cell instead of replacing the row; columns gained sortable + rowClass, which collide with same-named consumer fields.
244
+ - [Migration v2→v3: BbTable](./guides/migration/components/bb-table.md) - The table renders as a CSS grid of divs with ARIA roles, not a <table> — every element selector (table/thead/tbody/tr/th/td) under .bb-table matches nothing, #thead/#tbody/#tfoot take role-complete div rows, --fill/--natural-width are gone, column widths drift within documented bounds, copy-to-spreadsheet and print header repetition degrade; allowSelectAll inverted to disableSelectAll; loading no longer blanks populated tables and now inerts rows; #loading is first-load-only; #no-data fills a full-width cell instead of replacing the row; row events drop the selected argument; columns gained sortable + rowClass, which collide with same-named consumer fields.
245
245
  - [Migration v2→v3: BbTabs (was BbTab)](./guides/migration/components/bb-tabs.md) - Component renamed; querykey→queryKey; animations default on; label slots renamed and all dynamic names/URL slugs normalized; the entire .bb-tab BEM tree renamed to .bb-tabs / .bb-tabs-list / .bb-tabs-panes.
246
246
  - [Migration v2→v3: BbTag](./guides/migration/components/bb-tag.md) - modelValue is strictly string[] now, multiple is removed, and duplicates are rejected case-insensitively by default.
247
247
  - [Migration v2→v3: BbTextInput](./guides/migration/components/bb-text-input.md) - The type prop narrows to the text-like input types — number/date values must move to their dedicated components; a bare `floating` attribute was never a prop, the spelling is label-mode="floating".
@@ -648,7 +648,11 @@ paints a selected row but opens nothing is a dead affordance.
648
648
 
649
649
  `.plain-link`, `.table-flush` and the row tints are project CSS by design (the
650
650
  library ships no such classes), but they are the _same_ project CSS on every
651
- workspace page. Author them **once**, next to the layout, and import that file
651
+ workspace page. (`.table-flush`'s edge-inset rules address the table through
652
+ its grid containers — `> .bb-table__table > .bb-table__body > [role='row'] >
653
+ :first-child` — never through `table`/`tbody`/`tr`/`td`, which the table no
654
+ longer renders; the canonical block is in the BbTable guide § Styling hooks.)
655
+ Author them **once**, next to the layout, and import that file
652
656
  from the app entry — not per page, and never copy-pasted into a page's
653
657
  `<style scoped>`, which is how three pages end up with three underline colours.
654
658
 
@@ -72,10 +72,12 @@ OwnerPaginator::make(...), 'tabCounts' => [...], 'ownerOptions' => [...]])` —
72
72
  parent").
73
73
  - **Page-level cell-padding overrides are scoped with direct-child
74
74
  combinators.** The full-bleed treatment (`.table-flush`) pads the outer
75
- table's first/last cells via `:deep(> table > thead > tr > th:first-child)`
76
- — a bare descendant selector would also hit the nested table and clobber
77
- the structural offset padding it computes to stay flush with the parent
78
- grid.
75
+ table's first/last cells via
76
+ `:deep(> .bb-table__table > .bb-table__head > [role='row'] > :first-child)`
77
+ — the table is a CSS grid of roled `div`s, not a `<table>`, so the chain
78
+ runs through its container classes, and a bare descendant selector would
79
+ also hit the nested table (inside `.bb-table-expand__cell`) and clobber the
80
+ structural offset padding it computes to stay flush with the parent grid.
79
81
  - **Expansion is page state, not table-internal state.** `expanded` (owner
80
82
  ids) lives in the context and binds through `v-model:expanded-items`, so
81
83
  the toolbar (expand-all/collapse-all), the bulk bar (expand selected) and
@@ -190,7 +192,8 @@ one-off pixel inset per page:
190
192
 
191
193
  `--table-identity-lead` is the width of the identity cell's toggle track, so
192
194
  the two can never drift. Set the knob on the nest container — never pad the
193
- child's `tbody` alone, which reintroduces the edge mismatch the snap removed.
195
+ child's body (`.bb-table__body`) alone, which reintroduces the edge mismatch
196
+ the snap removed.
194
197
 
195
198
  ## The code
196
199
 
@@ -780,25 +783,42 @@ const rowClass = (item: Owner) => {
780
783
  }
781
784
 
782
785
  /* Direct-child combinators keep these overrides on the OWNER table's own
783
- cells. A bare `:deep(tbody td:first-child)` would also match the nested
784
- holdings table and clobber the structural offset padding it computes to
785
- stay flush with the parent grid — the one selector mistake that silently
786
- breaks nested-table alignment. */
787
- .table-flush :deep(> table > thead > tr > th:first-child),
786
+ cells. The table is a CSS grid of roled divs (no <table>/<tr>/<td>), so the
787
+ chain runs through its container classes. A bare
788
+ `:deep([role='cell']:first-child)` would also match the nested holdings
789
+ table and clobber the structural offset padding it computes to stay flush
790
+ with the parent grid — the one selector mistake that silently breaks
791
+ nested-table alignment. */
788
792
  .table-flush
789
- :deep(> table > tbody > tr > td:first-child:not(.bb-table-expand__cell)) {
793
+ :deep(> .bb-table__table > .bb-table__head > [role='row'] > :first-child),
794
+ .table-flush
795
+ :deep(
796
+ > .bb-table__table
797
+ > .bb-table__body
798
+ > [role='row']
799
+ > :first-child:not(.bb-table-expand__cell)
800
+ ) {
790
801
  padding-inline-start: var(--page-inset);
791
802
  }
792
803
 
793
- .table-flush :deep(> table > thead > tr > th:last-child),
794
804
  .table-flush
795
- :deep(> table > tbody > tr > td:last-child:not(.bb-table-expand__cell)) {
805
+ :deep(> .bb-table__table > .bb-table__head > [role='row'] > :last-child),
806
+ .table-flush
807
+ :deep(
808
+ > .bb-table__table
809
+ > .bb-table__body
810
+ > [role='row']
811
+ > :last-child:not(.bb-table-expand__cell)
812
+ ) {
796
813
  padding-inline-end: var(--page-inset);
797
814
  }
798
815
 
799
816
  /* Expand rows stay edge-to-edge so the nested table can inherit the parent
800
817
  tracks without an extra inset shifting everything sideways. */
801
- .table-flush :deep(> table > tbody > tr > td.bb-table-expand__cell) {
818
+ .table-flush
819
+ :deep(
820
+ > .bb-table__table > .bb-table__body > [role='row'] > .bb-table-expand__cell
821
+ ) {
802
822
  padding-inline: 0;
803
823
  }
804
824
 
@@ -1304,7 +1324,7 @@ export interface OwnerDetail extends Owner {
1304
1324
  widths.** `inherit-column-widths` is the whole alignment mechanism: the
1305
1325
  holdings table reads the owners table's measured tracks, so Resource sits
1306
1326
  under Owner and both $/mo columns share one right edge at every viewport
1307
- width. Two hand-maintained `<colgroup>`s (or two tables that merely look
1327
+ width. Two hand-maintained width lists (or two tables that merely look
1308
1328
  similar) drift apart the first time a label changes; the inheritance
1309
1329
  cannot.
1310
1330
  - **Every child column snaps, and the base is 1.** The parent is
@@ -1321,11 +1341,12 @@ export interface OwnerDetail extends Owner {
1321
1341
  - **Cell-padding overrides use direct-child combinators.** `.table-flush`
1322
1342
  bleeds the owner table past the page padding and re-adds the inset on the
1323
1343
  outermost cells — but only via
1324
- `:deep(> table > tbody > tr > td:first-child)`. A bare descendant
1325
- selector would also hit the nested table's cells and clobber the offset
1326
- padding it computes to stay on the parent grid. The expand cell itself is
1327
- excluded from the inset and zero-padded, so the nested table gets the full
1328
- row width to snap within.
1344
+ `:deep(> .bb-table__table > .bb-table__body > [role='row'] > :first-child)`
1345
+ (the table is a CSS grid of roled `div`s; `table`/`tbody`/`tr`/`td`
1346
+ selectors match nothing). A bare descendant selector would also hit the
1347
+ nested table's cells and clobber the offset padding it computes to stay on
1348
+ the parent grid. The expand cell itself is excluded from the inset and
1349
+ zero-padded, so the nested table gets the full row width to snap within.
1329
1350
  - **Expansion is rung-3 page state.** `expanded` lives in the context
1330
1351
  because four surfaces touch it: the per-row control, the toolbar's
1331
1352
  expand-all/collapse-all, the bulk bar's Expand, and the peek's "Expand in
@@ -814,7 +814,11 @@ const statusGroup: BbDropdownItem = {
814
814
 
815
815
  // Pipeline facet: `prefill: true` resolves a picked customer's name eagerly,
816
816
  // so a chip never sits on a bare id while this group's own submenu (inside
817
- // the Filters menu) has never been opened.
817
+ // the Filters menu) has never been opened. The eager flag belongs HERE, on
818
+ // the menu's copy only — the chip derives its own copy with
819
+ // `prefill: 'interaction'` (see FilterChip.vue), because a second dropdown
820
+ // redeclaring `prefill: true` would fetch every seeded facet twice on page
821
+ // load for nothing.
818
822
  const customerGroup: BbDropdownItem = {
819
823
  key: 'customer',
820
824
  label: 'Customer',
@@ -866,10 +870,10 @@ const menu = computed<BbDropdownItem[]>(() => [
866
870
  component sitting between `BbBadge` and `BbBadgeButton` breaks the
867
871
  badge's direct-child vnode scan (the companion must be a literal
868
872
  child). This IS "the chip is itself a dropdown onto the same filter
869
- group": `group` is the exact descriptor FilterRail declared, reused
870
- rather than redeclared, so this scoped picker and the Filters menu's
871
- own submenu can never drift apart. -->
872
- <BbDropdown :activator="chipRef" :items="[group]" :width="220">
873
+ group": `chipGroup` is derived from the exact descriptor FilterRail
874
+ declared (spread, one override — never redeclared), so this scoped
875
+ picker and the Filters menu's own submenu can never drift apart. -->
876
+ <BbDropdown :activator="chipRef" :items="[chipGroup]" :width="220">
873
877
  <BbDropdownGroup
874
878
  :id="groupKey"
875
879
  :model-value="modelValue"
@@ -903,9 +907,24 @@ const emit = defineEmits<{ 'update:modelValue': [value: unknown] }>();
903
907
 
904
908
  const chipRef = useTemplateRef('chipRef');
905
909
 
910
+ // The chip's own copy of the shared descriptor, one override: `prefill:
911
+ // 'interaction'`. BbDropdown mounts a pipeline resolver for every group it
912
+ // declares at its own root, independent of the panel ever opening — so if
913
+ // this second dropdown repeated the menu's `prefill: true` verbatim, each
914
+ // seeded pipeline facet would be fetched TWICE on page load, and the chip's
915
+ // copy of the result would feed nothing (the label below comes from the
916
+ // menu's resolution via context). `'interaction'` defers this copy's fetch
917
+ // to the moment the chip's dropdown is actually opened. For static array
918
+ // facets the override is inert — arrays resolve synchronously either way.
919
+ const chipGroup = computed<BbDropdownItem>(() => ({
920
+ ...props.group,
921
+ prefill: 'interaction',
922
+ }));
923
+
906
924
  // Reads the Filters menu's already-resolved selection — no twin fetch. A
907
- // pipeline group needs `prefill: true` (set on `group` by FilterRail) so this
908
- // never sits on a bare id/value while the menu's own submenu is unopened.
925
+ // pipeline group needs `prefill: true` (set on the MENU's copy of `group` by
926
+ // FilterRail) so this never sits on a bare id/value while the menu's own
927
+ // submenu is unopened.
909
928
  const dropdownFilters = useBbDropdownContext('invoice-filters');
910
929
  const chipText = computed(() => {
911
930
  const options =
@@ -648,7 +648,11 @@ paints a selected row but opens nothing is a dead affordance.
648
648
 
649
649
  `.plain-link`, `.table-flush` and the row tints are project CSS by design (the
650
650
  library ships no such classes), but they are the _same_ project CSS on every
651
- workspace page. Author them **once**, next to the layout, and import that file
651
+ workspace page. (`.table-flush`'s edge-inset rules address the table through
652
+ its grid containers — `> .bb-table__table > .bb-table__body > [role='row'] >
653
+ :first-child` — never through `table`/`tbody`/`tr`/`td`, which the table no
654
+ longer renders; the canonical block is in the BbTable guide § Styling hooks.)
655
+ Author them **once**, next to the layout, and import that file
652
656
  from the app entry — not per page, and never copy-pasted into a page's
653
657
  `<style scoped>`, which is how three pages end up with three underline colours.
654
658