32 KiB
Bootstrap v5 → v6: analysis and a phased plan for Tabler 2.0
Status: analysis, decisions and task board. Nothing implemented yet beyond the Floating UI branch.
Source of truth: local Bootstrap checkout at ~/htdocs/bootstrap, branch v6-dev
(6.0.0-alpha1, 495 commits ahead of main/5.3.8, work from 2025-08-26 to 2026-07-22).
Primary reference: site/src/content/docs/guides/migration.mdx (539 lines).
Upstream also ships skills/bootstrap-v5-v6-migration, a phase-by-phase migration skill for
coding agents — worth reading as a ready-made checklist.
Diff size: scss/ 150 files, +11 585 / −8 889. js/src/ 36 files, +6 208 / −2 149.
Tabler's exposure: core/scss/bootstrap/ holds ~5 000 lines of vendored upstream Sass and
core/js/src/bootstrap/ ~4 900 lines of a vendored TypeScript port of the v5 plugins.
1. What actually changed upstream
1.1 Class naming moved from infix to prefix
Every responsive and state class now uses a Tailwind-style prefix:class pattern.
| v5 | v6 |
|---|---|
.col-md-6 |
.md:col-6 |
.d-md-none |
.md:d-none |
.navbar-expand-md |
.md:navbar-expand |
.offset-md-2, .g-md-3 |
.md:offset-2, .md:g-3 |
.container-sm |
.sm:container |
.table-responsive-md |
.md:table-responsive |
.list-group-horizontal-md |
.md:list-group-horizontal |
.sticky-md-top |
.md:sticky-top |
.opacity-50-hover:hover |
.hover:opacity-50 |
.d-print-none |
.print:d-none |
.dialog-fullscreen-sm-down |
.sm-down:dialog-fullscreen |
In Sass, breakpoint-infix() became breakpoint-prefix() and returns "md\:";
loop-breakpoints-up / loop-breakpoints-down expose $prefix instead of $infix.
Breakpoints themselves moved: lg 992 → 1024, xl 1200 → 1280, xxl renamed to 2xl and
1400 → 1536. Containers: xl 1140 → 1200, 2xl 1320 → 1440. $grid-breakpoints → $breakpoints.
1.2 Sass architecture
_variables.scss,_variables-dark.scssand_maps.scssare gone. New files:_config.scss(all variables),_colors.scss,_theme.scss, and a rewritten_root.scss.- CSS cascade layers declared in
_root.scssand applied across every partial. Upstream hit the same limitation we did: Sass will not allow@use/@forwardinside@layer. $prefixwas removed from Sass. Custom properties are authored unprefixed and prefixed at build time bypostcss-prefix-custom-properties— the same approach Tabler already shipped in 1.5 (.build/build-css.ts).- Node Sass support dropped. RFS removed entirely (
scss/vendor/_rfs.scssdeleted); responsive type now usesclamp(). - New directory grouping with
index.scssper group:scss/layout/,scss/content/,scss/buttons/,scss/forms/,scss/mixins/,scss/helpers/. - All
$*-focus-box-shadowvariables removed in favour of onefocus-ring()mixin driven by--focus-ring*custom properties. $enable-dark-moderemoved — dark mode is always compiled, switched at runtime viadata-bs-theme.
1.3 Colour system
- Every colour variable is
oklch(); tint/shade scales are generated withcolor-mix(in lab, …)in the compiled CSS. - All
--*-rgbcustom properties andrgba()patterns are gone. This is a hard browser requirement onoklch()andcolor-mix(). - A
.theme-*class sets--theme-bg,--theme-fg,--theme-border,--theme-contrastand components only read those tokens. Per-colour component classes are replaced by composition:.btn-primary→.btn-solid .theme-primary.btn-outline-primary→.btn-outline .theme-primary.badge.bg-primary→.badge-subtle .theme-primary- the same for alerts, tables, cards, accordions and pagination
- New button variants:
.btn-solid,.btn-outline,.btn-subtle,.btn-text,.btn-styled,.btn-icon, plus a.btn-xssize. Buttons and inputs share--btn-input-*sizing tokens.
1.4 Components rebuilt on native browser APIs
This is the substantive part of the release, not the renames.
- Modal → Dialog on the native
<dialog>element withshowModal()/show()/close(). The.modal-dialog/.modal-contentwrappers and the.modal-backdropelement are gone (::backdropinstead), and with them the internalutil/backdrop,util/focustrapandutil/scrollbarhelpers — the browser supplies the backdrop, the focus trap and the inert top layer. Scroll lock is:root.dialog-openplusscrollbar-gutter: stable. - Offcanvas → Drawer, sharing a
DialogBaseclass with Dialog, plus swipe-to-dismiss and a.drawer-sheetvariant. - Responsive navbar uses Drawer, not Collapse. The toggler targets a
<dialog class="drawer">..navbar-light/.navbar-darkremoved. - Accordion on
<details>/<summary>, no JavaScript for open/close. Exclusive groups use the HTMLnameattribute instead ofdata-bs-parent..accordion-buttonand.accordion-collapseare gone. - Carousel on CSS scroll snap. No more
float+translateXor a custom swipe handler. New multi-slide, peek, gap and centre modes.ride→autoplay(boolean, opt-in),wrap→ends: loop|wrap|stop. Removed.carousel-caption,.carousel-dark,.carousel-control-prev/nextand the transitional.carousel-item-*classes. - Dropdown → Menu.
.dropdown-menu→.menu,.dropdown-item→.menu-item,data-bs-toggle="dropdown"→"menu", events*.bs.dropdown→*.bs.menu. The.dropdownwrapper and.dropdown-toggleclass are gone and markup flattens from<ul><li><a>to<div class="menu"><a class="menu-item">. Direction comes fromdata-bs-placement. - Icons (close button, navbar toggler, breadcrumb divider, carousel controls, checkbox marks) are
drawn with
mask-imagetinted bycurrentcolorinstead of embedded SVG or filters..btn-close-whiteand$btn-close-white-filterremoved. .card-groupand.*:list-group-horizontalswitched from media queries to container queries — without a query container (e.g..contains-inline) on a parent they stay stacked.
1.5 JavaScript
- ESM only. No UMD bundles, no
window.bootstrapglobal;<script type="module">is required. Data attribute APIs are unchanged. - Popper replaced by Floating UI (
@floating-ui/dom, a peer dependency). ThepopperConfigoption is nowfloatingConfig. - Vanilla Calendar Pro added as a peer dependency for the new Datepicker.
- ScrollSpy rewritten on
IntersectionObserverwith an activation line (topMargin, default12%); the deprecatedoffsetandmethodoptions are gone. - New plugins:
Menu,Dialog,Drawer,Combobox,Chips,OtpInput,Strength,Range,Toggler,Datepicker,NavOverflow.
1.6 Utilities — the quiet breakage
Class names survive but their values change, so old markup compiles and silently looks wrong.
$spacersgrew from 0–5 to 0–9 and remapped: v53(1rem) is v64; v54(1.5rem) is v66; v55(3rem) is v69..rounded-Nregenerated from a numeric$radiimap (--radius-0…--radius-9); v5.rounded-1is roughly v6.rounded-3, v5.rounded-4is v6.rounded-8..fs-1….fs-6replaced by ascending t-shirt sizes.fs-xs….fs-6xl;.lh-base→.lh-md;.display-1….display-6and.leadremoved with no single-class replacement..text-*colour utilities →.fg-*;.text-bg-*,.bg-light,.bg-darkand.bg-body-*removed in favour of a neutral.bg-1….bg-4scale;.text-reset→.fg-reset..mh-*/.mw-*→.max-h-*/.max-w-*; new.min-h-*/.min-w-*.- Negative margins reduced to
.ms--1,.ms--2,.me--1,.me--2. - Spacing and border utilities now emit logical properties (
margin-block-start,padding-inline-start,border-inline-end) with unchanged class names. - Layered, themeable shadows:
.shadow-xs….shadow-xl,.shadow-{color},.shadow-opacity-*. link-*utilities folded intounderline-*.- New
.contains-inline/.contains-sizecontainer-query utilities,.grid-cols-*,.place-items,.justify-items,.border-keyline(0.5px).
1.7 Forms
.form-selectremoved — use.form-controlon<select>._form-check.scsssplit into_checkbox/_radio/_switch;.checkgoes directly on the<input>with no wrapper..btn-checkmoves to the<label>with a nested input, using:has()instead ofid/forpairs.- Validation rewritten: no
.was-validated, no bare:valid/:invalid. Opt in withdata-bs-validateon the<form>and style:user-invalid.$enable-validation-iconsand all background validation icons removed.$form-validation-states→$validation-states. .form-rangebecomes a wrapper; the input takes.form-range-inputand the component is JavaScript-driven.- New
.form-field/.form-groupgrid layout primitive (label + control + help + feedback). Input groups loseflex-wrapand.has-validation.
1.8 Bootstrap moved onto Tabler's turf
v6 adds components Tabler already ships under the same names: avatar (core/scss/ui/_avatars.scss),
chip (_chips.scss), stepper (Tabler's _steps.scss), plus progress and status work.
It also adds ones Tabler does not have: otp-input, datepicker, combobox, prose,
form-adorn, password strength, nav-overflow, form-field.
The class names collide while the APIs and tokens do not. Before 2.0 we have to decide, per
component, whose definition of .avatar, .chip and .stepper wins.
2. Fit with the agreed 2.0 direction
The decision on record for 2.0 is: our own core, v5 class names with v6 architecture, no wholesale port of v6. Against that, the upstream diff splits fairly cleanly.
Take:
_config / _colors / _theme / _root split with the defaults() + tokens() pattern ·
cascade layers · PostCSS prefixing (already shipped in 1.5) · one focus-ring() mixin ·
logical properties · native <dialog> and <details> (removes JavaScript and fixes several
findings from our a11y audit) · Floating UI (already in progress on
migrate-popper-to-floating-ui) · the $radii scale · scroll-snap carousel.
Skip or defer:
the md: prefix syntax · the modal→dialog, offcanvas→drawer, dropdown→menu renames
(take the implementations, keep the v5 names) · the $spacers and .rounded-* remapping ·
.text-* → .fg-*.
Cannot be taken halfway:
oklch() + color-mix() and the death of --*-rgb — they are the foundation of the .theme-*
system and every colour variant, so adopting the v6 token architecture means adopting the browser
requirement too. Same for ESM-only JavaScript.
3. Phased migration plan
Phases are ordered by dependency. All 2.0 work targets the long-lived v2-dev branch (already on
origin); each phase lands as its own feature branch cut from v2-dev and merged back by PR, never
into dev, which is frozen for 1.x. Every phase that
claims to be output-neutral must be proved with the html-diff harness; every phase that changes
class emission must go through check-markup-classes.
Phase 0 — Decisions and prerequisites
No code. Settle the things that later phases cannot proceed without.
- Browser baseline:
oklch()andcolor-mix()are in. Phases 2 and 3 can start. - Distribution: ESM only, the UMD
tabler.jsbundle is dropped. Phase 5 can start. - Class-name policy: confirm v5 names stay, and write down the exceptions we are willing to make.
- Component namespace policy for
.avatar/.chip/.stepper: unify with upstream (see phase 9). - Read
~/htdocs/bootstrap/skills/bootstrap-v5-v6-migration/SKILL.mdand lift anything reusable into our own migration notes.
Deliverable: a short decision record; 2.0 upgrade guide skeleton in UPGRADE.md; this document
committed on v2-dev (it currently sits there untracked).
Phase 1 — Sass module architecture
Restructure without changing a single byte of compiled CSS.
- Split
core/scss/_variables.scss,_variables-dark.scssand_maps.scssinto_config.scss,_colors.scss,_theme.scss, reworking_props.scss/_rootaccordingly. - Add
index.scssper directory (ui/,forms/,mixins/,helpers/,layout/) and routetabler.scssthrough them. - Introduce the
defaults()+tokens()pattern so compile-time and runtime overrides are one mechanism. - Keep
@layerout of this phase — it is a separate, previously withdrawn prototype.
Files: core/scss/_variables.scss, _variables-dark.scss, _maps.scss, _config.scss,
_props.scss, _settings.scss, _core.scss, tabler.scss.
Gate: html-diff must show zero diff; byte-diff of core/dist/css/tabler.css.
Risk: low mechanically, high in volume. This is the phase where @use … as * writing back to
config bites, per the existing SCSS module notes.
Phase 2 — Colour system on oklch and theme tokens
The point of no return for browser support, and we take it. The 2.0 browser baseline is whatever
supports oklch() and color-mix(); write that baseline into the upgrade guide.
- Convert colour variables to
oklch(); generate tint/shade withcolor-mix(in lab, …). - Introduce
--theme-bg/--theme-fg/--theme-border/--theme-contrastand a.theme-*class family. - Remove
--tblr-*-rgband everyrgba(var(--tblr-*-rgb), …)call site. - Decide whether Tabler's colour variants keep emitting
.btn-primaryetc. as thin compositions over.theme-primary, so v5 markup keeps working.
Files: core/scss/_colors.scss (new), _theme.scss (new), _props.scss, all of
core/scss/ui/** that uses -rgb.
Gate: contrast gate from the a11y work; visual review of dark mode; html-diff will legitimately
differ here, so review it by hand.
Risk: high. The earlier standalone light-dark() / color-mix() attempt (issue #2720, branch
dev-scss-light-dark) is abandoned; this phase supersedes it, so reuse its lessons (cascade order
of color-scheme, the token-dump verification harness) but not its code.
Phase 3 — Token scales
- Adopt the numeric
$radiimap and--radius-0…--radius-9, keeping.rounded-*mapped to the v5 values so existing markup does not shift. - Decide on
$spacers: adopting the v6 0–9 scale silently changes every.p-3/.m-4in every Tabler template. Recommendation: keep the v5 scale, add finer steps at new keys. - Replace all
$*-focus-box-shadowwith onefocus-ring()mixin over--focus-ring*tokens. - Consider the layered shadow scale (
.shadow-xs….shadow-xl) and.border-keyline. - Skip the
.fs-*renaming — it inverts the scale direction and breaks every page.
Files: core/scss/_config.scss, _props.scss, core/scss/mixins/**, core/scss/_utilities.scss.
Gate: html-diff zero diff for the radius and focus-ring work.
Phase 4 — Logical properties
Already scoped on the migrate-css-physical-directions-to-logical branch. Upstream did the same,
which validates the direction.
- Emit
margin-block-*/padding-inline-*/border-inline-*from spacing and border utilities with unchanged class names. - Reconcile with the
--tblr-dirmultiplier work fortranslateX, and keep the/*rtl:ignore*/workaround for rtlcss negatingcalc().
Gate: RTL build comparison; html-diff zero diff.
Status: #2974 landed the block axis of the spacing and border utilities (mt / mb / my,
pt / pb / py, .border-top / .border-bottom / .border-y, negative mt / mb / my) plus
the non-geometry physical leftovers (_extends.scss markdown-table padding, _spinners.scss border).
The inline axis was already logical. tabler.css changed by property rename only; tabler.rtl.css
changed by the same renames plus the three inline-axis sweep lines, which rtlcss no longer flips
because the logical property now resolves the direction itself — rendered output is unchanged.
Why rtlcss and --dir stay. Logical properties cover box-model sides (margin, padding, border,
inset, size) but there is no logical form for transform, so a physical translateX(-50%) cannot be
mirrored by the browser. Tabler bridges that with --tblr-dir (1 in LTR, -1 in RTL, set in
_props.scss) multiplied into the calc() of every direction-sensitive translate — .translate-middle
in _utilities.scss, $form-floating-label-transform in _variables.scss — each carrying a
/*rtl:ignore*/ so rtlcss (which would otherwise negate the calc() a second time) leaves it alone.
rtlcss is still run in .build/build-css.ts for everything --dir does not reach.
What would let us drop rtlcss. Every remaining rtl: marker under core/scss has to be gone
first — grep -rn 'rtl:' core/scss --include='*.scss' is the live checklist. They fall into:
- Transforms —
.translate-middle,$form-floating-label-transform, and thetranslateX/rotateuses inui/_steps.scss,ui/_switch-icon.scss,ui/_buttons.scss,ui/_badges.scss,ui/_loaders.scss,ui/_dropdowns.scss,layout/_navbar.scss,bootstrap/_carousel.scss,bootstrap/_offcanvas.scss,bootstrap/_spinners.scss. These need the--dirmultiplier applied the same way, or a future CSS logical transform. - Popover / tooltip arrow geometry (
bootstrap/_popover.scss,_tooltip.scss) — physicalborder-*triangles on absolutely positioned arrows; the Floating UI work (phase 5) is expected to replace this with placement-driven values. rtl:rawblocks inbootstrap/_reboot.scssandui/_breadcrumbs.scss, and thertl:begin:removeblock inmixins/bootstrap/_utilities.scss— hand-written RTL overrides that would need a logical-property equivalent.
Once the checklist is empty, rtlcss and the tabler.rtl.css build come out and one stylesheet
serves both directions, matching Bootstrap v6.
Phase 5 — JavaScript runtime
- ESM only. Drop the UMD build from
core/package.json(js:build:standalone,js:min:*); no compatibility bundle.<script type="module">becomes the documented way to loadtabler.js, and thewindow.tablerglobal goes with the bundle. - Finish the Floating UI migration (#2966). The option is
positionConfig, not v6'sfloatingConfig;popperConfigstays as a deprecated alias for one major. - Take the ScrollSpy rewrite (
IntersectionObserver+ activation line) intocore/js/src/bootstrap/scrollspy.ts. - Prepare to delete
util/backdrop.ts,util/focustrap.ts,util/scrollbar.ts— but only after phase 6 lands, since they are what the native<dialog>replaces.
Files: core/js/src/bootstrap/** (~4 900 lines), core/js/src/tabler.ts, core/package.json,
core/.build/vite.config.mts.
Gate: core vitest suites; browser smoke test of every preview page that initialises a plugin.
Phase 6 — Native components
The phase with the best effort-to-value ratio: less code, better accessibility, same class names.
- Rebuild the modal on
<dialog>while keeping.modal/.modal-header/.modal-body/data-bs-toggle="modal". Watch for Tabler's own modal variants and thepreview/modal architecture. - Rebuild offcanvas on the same
DialogBase. - Rebuild the accordion on
<details>/<summary>;.accordion-buttondisappears, so the docs class reference and every accordion example need updating (~126 occurrences across 6 files). - Rebuild the carousel on CSS scroll snap.
- Consider the responsive navbar moving from Collapse to a drawer — but Tabler's navbar and sidebar are heavily customised, including the 1.5 folded sidebar, so evaluate this separately.
- Convert close button, navbar toggler and breadcrumb divider icons to
mask-image+currentcolor.
Files: core/scss/bootstrap/_modal.scss, _offcanvas.scss, _carousel.scss,
core/scss/ui/_modals.scss, _offcanvas.scss, _carousel.scss, _accordion.scss, _close.scss,
core/js/src/bootstrap/{modal,offcanvas,carousel,collapse}.ts, plus shared/ui/** and
preview/pages/** markup.
Gate: html-diff will differ by design — review page by page; keyboard and screen-reader passes.
Risk: high, but each component can ship independently.
Phase 7 — Forms
.form-selectstays as it is in 2.0; nothing in markup, docs or SCSS changes for it. Revisit after 2.0.- Split
_form-check.scssinto checkbox / radio / switch, with.checkdirectly on the input. - Move validation to
data-bs-validate+:user-invalid; drop the built-in validation icons. - Make
.form-rangea wrapper with a JavaScript-driven filled track. - Evaluate
.form-fieldas a layout primitive against Tabler's existing form markup.
Files: core/scss/bootstrap/forms/**, core/scss/ui/forms/**, shared/ui/ form components,
docs/content/** form pages.
Risk: medium-high, and the most visible to template users.
Phase 8 — Utilities and the responsive naming question
Kept last because it is the most disruptive and the most optional.
- Container-query utilities (
.contains-inline,.contains-size) and grid utilities (.grid-cols-*,.place-items,.justify-items) are pure additions — take them. - The
md:prefix syntax is not adopted in 2.0. Adopting it would mean rewriting roughly 1 800 class occurrences across ~200 files inpreview/,docs/andshared/, and breaking every third-party Tabler template. v5 infix names (.col-md-6,.d-md-none) stay. - Revisit for 3.0 at the earliest; if ever adopted, ship both spellings for one major and add a codemod.
- Breakpoint value changes (
lg1024,xl1280,2xl1536) can be considered independently of the naming, but they reflow every layout, so treat them as their own decision.
Gate: check-markup-classes is the gate that makes any of this reviewable.
Phase 9 — Component namespace collisions
.avatar, .chip, .stepper (Tabler's .steps), .progress and .status are unified with
upstream. Tabler's definition is reworked so that the class names,
modifiers and tokens are compatible with Bootstrap v6's component of the same name; Tabler-only
extras stay as additive modifiers on top. Where Tabler's current name differs (.steps vs
.stepper) the v6 name is adopted and the old class stays as an alias for one major, listed in the
upgrade guide. This is the one deliberate exception to "v5 names stay", because these components
have no v5 name to keep. Still to decide, per component, is whether to
implement v6's genuinely new components ourselves: otp-input, combobox, datepicker,
password strength, prose, form-adorn, nav-overflow, form-field.
Note that our datepicker plans and upstream's choice of Vanilla Calendar Pro may or may not agree — worth checking before committing to a library.
Deliverable: a per-component table (Tabler class, v6 class, what changes, what is aliased) in
the 2.0 planning notes, produced with the bootstrap-v6-reference agent before any SCSS moves.
Phase 10 — Documentation and gates
- Write the 2.0 upgrade guide the way 1.5's was written, with before/after for each breaking change.
- Update
classnamesfront matter acrossdocs/content/**for every class that moved. - Extend
check-markup-classesbaselines. - Add changesets per phase.
4. Suggested ordering
Phase 0 decisions
↓
Phase 1 Sass architecture ──────────────┐
↓ │
Phase 2 oklch + theme tokens │ (Phase 5 JS can run in parallel
↓ │ from the start, except for the
Phase 3 token scales │ util/* deletions, which wait
↓ │ on Phase 6)
Phase 4 logical properties ─────────────┘
↓
Phase 6 native components (per component, independently shippable)
↓
Phase 7 forms
↓
Phase 8 utilities / naming decision
↓
Phase 9 component ownership
↓
Phase 10 docs and gates
5. Decisions and open questions
Decided:
- Browser baseline: 2.0 requires
oklch()andcolor-mix(). - ESM only. The UMD bundle is dropped in 2.0.
.form-selectis untouched in 2.0..avatar/.chip/.stepper/.progress/.statusare unified with Bootstrap v6's components; old names aliased for one major (phase 9).- The option that replaces
popperConfigispositionConfig(#2966);popperConfigis kept as a deprecated alias. v6'sfloatingConfigis not adopted.
Open:
$spacers: keep the v5 scale, or accept the silent reflow of every template?- Does the v6 navbar-as-drawer pattern survive contact with the folded sidebar from 1.5?
- Classes that native components remove (
.accordion-button,.accordion-collapse,.carousel-caption,.carousel-control-*,.btn-close-white): keep on the new element, keep as an empty alias, or drop with an upgrade-guide entry? The policy rule promises every 1.x class. - When cascade
@layerlands — it is adopted by the policy but has no phase and a withdrawn prototype. - Breakpoint values (
lg1024,xl1280,2xl1536): separate from the naming, undecided. - Forms validation (
data-bs-validate,:user-invalid, no.was-validated) and the_form-checksplit — visible to every template user, not yet decided. - Datepicker library: upstream picked Vanilla Calendar Pro; do we follow?
data-bs-*→data-tblr-*: does 2.0 switch docs and markup and dropdata-bs-*, or only keep the alias?
6. Task board
One PR per task unless noted. Size: S under a day, M a few days, L a week or more. "Gate" is the
proof the PR must carry per .agents/rules/v2.mdc. Order inside a group is the suggested order.
6.1 Ready now — no open question involved
Start with the light ones, in this order: #2969, #2977, #2970, #2971, #2972, #2973, #2974, then #2975 and #2976 with their docs parts left out. Documentation (the upgrade guide, the docs pass and the docs halves of #2975 / #2976) is deliberately last; the heavy phases (Sass split, oklch, component parity, native components) wait until the light ones are in and get their issues then. The Floating UI PR #2966 is parked.
| Issue | Phase | Task | Files | Gate | Size |
|---|---|---|---|---|---|
| #2966 (PR) | 5 | Finish Floating UI: apply the parked boundary: viewport/document fix (stash on the branch, tests green), reworded changeset, merge |
core/js/src/bootstrap/util/floating-ui.ts, dropdown.ts, tooltip.ts |
vitest, tsc | S |
| #2969 | 1 | _index.scss per directory (ui/, forms/, mixins/, layout/, utils/) and route tabler.scss through them |
core/scss/**/_index.scss, tabler.scss |
byte-identical tabler.css |
S |
| #2977 | 1 | defaults() and tokens() mixins; card, badge and avatar converted to per-component token maps ($card-tokens rendered on .card) as the reference pattern; further components as small follow-up PRs |
core/scss/mixins/**, core/scss/ui/_cards.scss, _badges.scss, _avatars.scss, _variables.scss |
byte-identical tabler.css, SCSS unit tests |
S |
| — | 1 | Split _variables / _variables-dark / _maps into _config / _colors / _theme / _root, introduce defaults() + tokens() |
core/scss/_variables.scss, _variables-dark.scss, _maps.scss, _props.scss, _settings.scss, _core.scss |
byte-identical tabler.css, zero html-diff |
L |
| #2972 | 3 | One focus-ring() mixin over --focus-ring* tokens, replacing 46 *-focus-box-shadow sites; same values for now |
core/scss/mixins/**, core/scss/ui/**, _config.scss |
byte-identical tabler.css |
M |
| #2970 | 3 | Numeric $radii map and --radius-0…9, with $border-radius-* and .rounded-* mapped to today's values |
_config.scss, _props.scss, _utilities.scss |
byte-identical tabler.css |
S |
| #2974 | 4 | Logical properties on the block axis of spacing and border utilities (inline axis is already logical), class names unchanged; keep rtlcss and --dir for transforms |
core/scss/_utilities.scss, _extends.scss, bootstrap/_spinners.scss |
diff limited to renamed properties in tabler.css and tabler.rtl.css, zero html-diff |
S–M |
| #2973 | 5 | ScrollSpy on IntersectionObserver with an activation line; drop the deprecated offset and method options |
core/js/src/bootstrap/scrollspy.ts, its spec |
vitest, preview smoke | M |
| #2976 | 5 | ESM only: remove the UMD scripts from core/package.json, fix exports, document <script type="module"> and the loss of window.tabler in the upgrade guide |
core/package.json, core/.build/vite.config.mts, docs/content/** getting started |
build, preview pages still initialise plugins | M |
| #2975 | 8 | Additive utilities: .contains-inline / .contains-size, .grid-cols-*, .place-items, .justify-items, .shadow-xs…xl, .border-keyline |
core/scss/_utilities.scss, docs utility pages, classnames front matter |
check-markup-classes baseline extended |
M |
| #2971 | 6 | Framework icons drawn with mask-image + currentcolor; close button, toggler and breadcrumb are already done, so the scope is the form-control, select, carousel and validation SVGs and dropping their -dark variants; .btn-close-white kept as an empty alias |
core/scss/bootstrap/forms/**, bootstrap/_navbar.scss, _carousel.scss, ui/forms/**, ui/_close.scss |
visual review light/dark, check:css-vars |
S–M |
| — | 0 | Lift the reusable parts of upstream skills/bootstrap-v5-v6-migration/SKILL.md into our notes; start the 2.0 section of UPGRADE.md with the browser baseline (oklch, color-mix) and the ESM-only note |
UPGRADE.md, .agents/ |
none | S |
| — | 2 | Colours on oklch() + color-mix(in lab), --theme-bg/fg/border/contrast and .theme-*, remove the 52 --tblr-*-rgb sites; .btn-primary etc. keep emitting via the theme tokens |
_colors.scss, _theme.scss, _props.scss, core/scss/ui/** |
contrast gate, dark mode screenshots, hand-reviewed html-diff |
L |
| — | 9 | Component parity table for .avatar, .chip, .steps→.stepper, .progress, .status against v6 (via the bootstrap-v6-reference agent); then one rework PR per component with the old names aliased |
planning notes, then core/scss/ui/_avatars.scss, _chips.scss, _steps.scss, _progress.scss, _status.scss |
check-markup-classes, classnames, changeset |
S + 5×M |
| — | 6 | Modal on native <dialog> under the v5 names, events and data attributes; keep Tabler's modal variants and the preview modal architecture working |
core/scss/bootstrap/_modal.scss, core/scss/ui/_modals.scss, core/js/src/bootstrap/modal.ts, shared/ui/** |
a11y checklist (focus trap, Escape, backdrop, focus return), by-design html-diff |
L |
| — | 6 | Offcanvas on the same DialogBase |
core/scss/bootstrap/_offcanvas.scss, core/js/src/bootstrap/offcanvas.ts |
same as the modal | M |
| — | 6 | Carousel on CSS scroll snap; autoplay and ends options; removed classes handled per question 8 |
core/scss/bootstrap/_carousel.scss, core/js/src/bootstrap/carousel.ts |
preview smoke, keyboard pass | M |
| — | 5 | Delete util/backdrop.ts, util/focustrap.ts, util/scrollbar.ts once the modal and offcanvas rebuilds are merged |
core/js/src/bootstrap/util/** |
vitest | S |
| — | 10 | 2.0 upgrade guide with before/after per breaking change, classnames updates, extended check-markup-classes baselines, changesets per phase |
UPGRADE.md, docs/content/** |
docs build, link gate | M, grows with each task |
Dependencies: #2977 after #2969; the Sass split after #2977. #2972 and #2970 can land before the
Sass split (in _variables.scss, moved later). oklch after the Sass split; the component parity
reworks after oklch (they use theme tokens). Modal → offcanvas → deleting the util/* helpers. The
upgrade guide runs alongside everything.
6.2 Waiting on a decision (section 5)
| Open question | Task once answered |
|---|---|
4 $spacers |
either nothing, or a remap PR plus a codemod for every template |
| 6 navbar as drawer | prototype against the folded sidebar, then decide |
| 8 classes removed by native components | accordion on <details> (phase 6, M, no issue yet) is blocked on this: it decides what happens to .accordion-button / .accordion-collapse (195 occurrences in 12 files) |
9 cascade @layer |
one PR after the Sass split, byte-diff plus specificity review |
| 10 breakpoint values | either nothing, or a reflow PR with screenshots of every preview page |
11 forms validation and _form-check split |
phase 7 PRs |
| 12 datepicker library | new component or nothing |
13 data-tblr-* switch |
docs and markup sweep, or nothing |