Migration

Track and review changes to the Bootstrap source files, documentation, and components to help you migrate from v5 to v6.

Upgrade

Modus Bootstrap 6 is a major release with many breaking changes to modernize our codebase, adopt newer build tools, and improve customization. Keep reading for a guide on how to migrate from v5 to v6, and a full changelog of what’s new.

  1. Bump your Bootstrap dependency:

    JSON
    {
      "dependencies": {
        "bootstrap": "^6.0.0"
      }
    }
  2. If using all of Bootstrap’s Sass files, include it in your Sass using @use:

    SCSS
    @use "bootstrap/scss/bootstrap";

    With this, you can then easily override Bootstrap’s Sass variables and maps:

    SCSS
    @use "bootstrap/scss/bootstrap" with (
     $spacer: 1rem,
     $enable-reduced-motion: true,
    );
  3. If using only certain parts of Bootstrap’s Sass files, you can use @use to import them individually. Be aware that our Sass file structure has changed and you may need to adjust your imports accordingly.

    SCSS
    @use "bootstrap/scss/forms";
  4. Update HTML and CSS per the changelog and updates in the documentation.

  5. Recompile your Sass to see the changes.

Changelog

CSS

  • Clarified and simplified CSS-Sass setup. Read more about our approach for how we use Sass and CSS together to build and customize projects for your specific needs, in particular how we use CSS variables inside Sass maps as our first-class customization layer.
  • Implemented CSS layers in _root.scss and applied them to all our Sass files.
    • Layers are set in _root.scss and then utilized across separate Sass partials.
    • We cannot, unfortunately, wrap @use or @forward statements in @layer—Sass expects those to be top level at all times. Also, while CSS allows @import "file.css" layer(name), Sass also does not support that.
  • New, streamlined color modes and theming.
    • Removed _maps.scss
    • Removed _variables-dark.scss
    • Added _colors.scss, splitting colors out to their own file
    • Removed _variables.scss, consolidating all variables into _config.scss
    • Added _theme.scss where we setup all our global theming for how colors are applied
  • Updated lg, xl, and 2xl breakpoints and containers.
    • Increased the lg breakpoint from 992px to 1024px; its container remains the same at 960px.
    • Increased the xl breakpoint from 1200px to 1280px, and its container from 1140px to 1200px.
    • Renamed xxl to 2xl for better scaling with additional custom breakpoints
    • Increased the 2xl breakpoint from 1400px to 1536px, and its container from 1320px to 1440px.
  • Adopted modern CSS color functions. All Sass color variables now use oklch() notation (e.g., $blue: oklch(60% 0.24 240)) and tint/shade scales are generated with color-mix(in lab, ...) in the compiled CSS. The v5 $*-rgb CSS custom properties and rgba() patterns have been removed. This requires browser support for color-mix() and oklch().
  • New theme token system with .theme-* classes. Per-component color variant classes (like .alert-primary, .badge.bg-primary, .btn-primary, .table-primary) are replaced by a composable .theme-{name} pattern. Adding .theme-primary to a component sets --theme-bg, --theme-fg, --theme-border, --theme-contrast, and other semantic CSS custom properties that the component reads. This applies across buttons, badges, alerts, tables, cards, accordions, and more.
  • Responsive and state classes now use a prefix instead of an infix or suffix. Class names follow the Tailwind-style prefix:class pattern (e.g., md:d-none instead of d-md-none, hover:opacity-50 instead of opacity-50-hover). In HTML, use the unescaped colon: class="md:d-none". This applies to utilities, grid, pseudo-state variants, and all responsive components.
CategoryBefore (v5)After (v6)
Utilities.d-md-none, .p-lg-3.md:d-none, .lg:p-3
State variants.opacity-50-hover:hover.hover\:opacity-50:hover
Grid columns.col-md-6.md:col-6
Row columns.row-cols-md-3.md:row-cols-3
Offsets.offset-md-2.md:offset-2
Gutters.g-md-3, .gx-md-3.md:g-3, .md:gx-3
CSS Grid.g-col-md-4.md:g-col-4
Containers.container-sm.sm:container
Navbar.navbar-expand-md.md:navbar-expand
Drawer.offcanvas-md.md:drawer
Tables.table-responsive-md.md:table-responsive
List group.list-group-horizontal-md.md:list-group-horizontal
Sticky.sticky-md-top.md:sticky-top
Stacks.vstack-md.md:vstack
Dialog.dialog-fullscreen-sm-down.sm-down:dialog-fullscreen
Print.d-print-none.print:d-none

Sass

  • Dropped support for Node Sass, including no longer testing any of our source CSS against it.
    • Rearranged several Sass files in the process.
  • Removed add() and subtract() functions. Use calc() instead.
  • Removed create-css-vars() mixin (unused).
  • Renamed breakpoint-infix() to breakpoint-prefix(). The function now returns a prefix string (e.g., "md\:") instead of an infix (e.g., "-md"). The loop-breakpoints-up and loop-breakpoints-down mixins now expose $prefix instead of $infix. Update any custom Sass that calls these functions or mixins.
  • CSS variable prefixing now handled by PostCSS. The $prefix Sass variable has been removed. CSS custom properties are now written without a prefix in the Sass source and prefixed automatically via postcss-prefix-custom-properties during the build. To customize the prefix, update your PostCSS configuration instead of Sass.
  • Removed RFS (Responsive Font Sizes). The scss/vendor/_rfs.scss file and all RFS mixins have been removed. Typography now uses fixed rem values and clamp() for responsive sizing. If you relied on RFS for automatic font scaling, you’ll need to implement your own responsive typography or use clamp() directly.
  • Renamed Sass files for consistency. _placeholders.scss is now _placeholder.scss and _spinners.scss is now _spinner.scss. Update any individual @use imports for these files.
  • Standardized focus styles with focus-ring mixin. All component-specific *-focus-box-shadow Sass variables (e.g., $btn-focus-box-shadow, $input-focus-box-shadow, $accordion-button-focus-box-shadow) have been removed. Focus styles are now handled by a shared @mixin focus-ring() using --focus-ring, --focus-ring-width, --focus-ring-offset, and --focus-ring-color CSS custom properties. Customize focus styles by overriding these tokens in _root.scss instead of individual Sass variables.
  • Renamed $grid-breakpoints to $breakpoints.
  • Removed $enable-dark-mode. Dark mode support is always compiled; control it at runtime with data-bs-theme (or the color-mode() mixin) instead of toggling a Sass flag.
  • Removes all deprecated Sass variables and values:
    • Removed $nested-kbd-font-weight, no replacement.
    • Removed muted, black-50, and white-50 from text colors utilities map
    • Removed the carousel dark Sass variables ($carousel-dark-indicator-active-bg, $carousel-dark-control-icon-filter) and the .carousel-dark class—they’re not reassigned. Dark carousels now use data-bs-theme="dark" (see the carousel changes below). Carousel captions were removed entirely too.
    • Removed $btn-close-white-filter. The close button no longer uses a filter—its icon is a CSS mask painted with currentcolor, so it adapts to dark backgrounds automatically.
    • Removed $border-radius-xxl, use $border-radius-2xl.
    • Removed $text-muted for secondary color.
    • Removed $hr-bg-color for $hr-border-color and $hr-height for $hr-border-width.
    • Renamed $zindex-dropdown to $zindex-menu.
    • Removed unused $dropdown-header-padding for the -x/-y split variables.
    • Removed unused $accordion-button-focus-border-color.
    • Removed unused $tooltip-arrow-color.
    • Removed unused $popover-arrow-color and $popover-arrow-outer-color
    • Removed unused $alert-bg-scale, $alert-border-scale, and $alert-color-scale (replaced by theme tokens)
    • Removed unused $list-group-item-bg-scale and $list-group-item-color-scale (replaced by theme tokens)
  • Removed form validation Sass variables and files.
    • Removed scss/forms/_form-variables.scss. Feedback/tooltip Sass variables and validation icon SVG data URIs are gone. Validation styling now uses theme-derived CSS custom properties.
    • Renamed scss/mixins/_forms.scss to scss/mixins/_form-validation.scss. Contains only the form-validation-state-selector mixin.
    • Removed $enable-validation-icons from scss/_config.scss.
    • Replaced $form-validation-states with $validation-states (state name to theme key map).

JavaScript

  • Bootstrap’s JavaScript is now ESM-only. We no longer ship UMD bundles. All dist files (bootstrap.js, bootstrap.bundle.js, and their minified versions) are native ES modules. The plugin APIs themselves are unchanged—only how you load and reference them is different.
    • CDN <script> tags must add type="module":

      HTML
      <script type="module" src="bootstrap.bundle.min.js"></script>
    • In v5, the UMD bundle automatically created a window.bootstrap global. ES modules don’t do this, so there is no longer a bootstrap global object. If you called plugin APIs through the global namespace, you must update to explicit imports:

      Before (v5):

      JavaScript
      const tooltip = bootstrap.Tooltip.getOrCreateInstance(el)

      After (v6):

      JavaScript
      import { Tooltip } from './bootstrap.bundle.min.js'
      const tooltip = Tooltip.getOrCreateInstance(el)
    • Data attribute APIs are unchanged. If you only use data-bs-toggle, data-bs-dismiss, and other data attributes—without calling the JavaScript API directly—the only change you need is adding type="module" to your script tag. All data attribute behavior continues to work automatically.

    • For modern ESM-based bundler setups (Vite, Webpack 5, Parcel 2, Rollup, etc.), no changes are needed — import { Tooltip } from 'bootstrap' works as before and now supports full tree shaking. Projects still using CommonJS require() calls will need to update to ESM import syntax.

  • Removed the separate bootstrap.esm.js and bootstrap.esm.min.js files — bootstrap.js is now the ESM entry point.
  • Removed js/index.umd.js entry point.
  • Removed jQuery support and the js-test-jquery test target.
  • Replaced the Dropdown component with Menu. All .dropdown-* classes are now .menu-* classes, and data-bs-toggle="dropdown" is now data-bs-toggle="menu". See the Menu docs for full details.
    • Renamed CSS classes: .dropdown-menu to .menu, .dropdown-item to .menu-item, .dropdown-divider to .menu-divider, .dropdown-header to .menu-header, .dropdown-submenu to .submenu.
    • Removed the .dropdown-toggle class — menu toggles no longer require a toggle class.
    • Removed the .dropdown wrapper — no wrapper element is required. The toggle and .menu are direct siblings.
    • Simplified markup from <ul><li><a class="dropdown-item"> to a flat <div class="menu"><a class="menu-item"> structure.
    • Removed .dropdown-toggle-split — button group border radius for split menus is now handled automatically via :has(+ .menu).
    • Renamed the JavaScript export from Dropdown to Menu — update imports to import { Menu } from 'bootstrap'.
    • Renamed events: show.bs.dropdown to show.bs.menu, shown.bs.dropdown to shown.bs.menu, hide.bs.dropdown to hide.bs.menu, hidden.bs.dropdown to hidden.bs.menu.
    • Renamed the data key from bs.dropdown to bs.menu (affects Menu.getInstance() and Menu.getOrCreateInstance()).
  • Added new Combobox component. A searchable select built on top of Menu, with single and multi-select support. See the Combobox docs.
  • Replaced Popper.js (@popperjs/core) with Floating UI (@floating-ui/dom) for menu, tooltip, and popover positioning. The popperConfig option on Tooltip, Popover, and Menu (formerly Dropdown) has been renamed to floatingConfig. Update any custom positioning configuration accordingly.
  • Added Vanilla Calendar Pro (vanilla-calendar-pro) as a peer dependency for the new Datepicker component.
  • Removed the jspm configuration from package.json.
  • Added "sideEffects" metadata to package.json to enable tree shaking in bundlers while preserving the Data API event listeners that Bootstrap’s plugins register at the top level.
  • Added "exports" map to package.json for explicit subpath access to source, dist, and Sass files.

Components

  • Replaced the Modal component with Dialog. Dialog is built on the native <dialog> element, using showModal() / show() / close() browser APIs. The markup, classes, data attributes, events, CSS variables, and JavaScript API have all changed:
    • Markup: The .modal > .modal-dialog > .modal-content wrapper structure has been replaced by a single <dialog class="dialog"> element. Body sections use .dialog-header, .dialog-body, and .dialog-footer directly inside the <dialog>.
    • CSS classes: .modal.dialog, .modal-header.dialog-header, .modal-body.dialog-body, .modal-footer.dialog-footer, .modal-title.dialog-title. The .modal-dialog and .modal-content wrapper classes have been removed entirely.
    • Sizes: .modal-sm.dialog-sm, .modal-lg.dialog-lg, .modal-xl.dialog-xl, .modal-fullscreen.dialog-fullscreen.
    • Data attributes: data-bs-toggle="modal"data-bs-toggle="dialog", data-bs-dismiss="modal"data-bs-dismiss="dialog".
    • JavaScript: ModalDialog — update imports to import { Dialog } from 'bootstrap'.
    • Events: show.bs.modalshow.bs.dialog, shown.bs.modalshown.bs.dialog, hide.bs.modalhide.bs.dialog, hidden.bs.modalhidden.bs.dialog, hidePrevented.bs.modalhidePrevented.bs.dialog.
    • Data key: bs.modalbs.dialog (affects Dialog.getInstance() and Dialog.getOrCreateInstance()).
    • CSS variables: --modal-*--dialog-*.
    • Backdrop: The .modal-backdrop DOM element and the legacy util/backdrop helper are gone — Dialog uses the native ::backdrop pseudo-element with backdrop-filter: blur() support.
    • Body scroll prevention: .modal-open on <body>.dialog-open on the <body> element.
    • New variant classes: .dialog-slide-up, .dialog-slide-down (slide animations), .dialog-instant (no animation), .dialog-static (static backdrop bounce), .dialog-nonmodal (non-modal positioning), .dialog-scrollable.
    • Non-modal support: Set modal: false or data-bs-modal="false" for non-modal dialogs.
    • Dialog swapping: Triggers inside an open dialog can open a new dialog and close the current one automatically.
    • See the Dialog docs for full details.
  • Offcanvas renamed to Drawer. All class names, data attributes, events, CSS variables, and JavaScript APIs have been renamed:
    • CSS classes: .offcanvas.drawer, .offcanvas-start.drawer-start, .offcanvas-header.drawer-header, etc.
    • Data attributes: data-bs-toggle="offcanvas"data-bs-toggle="drawer", data-bs-dismiss="offcanvas"data-bs-dismiss="drawer"
    • JavaScript: bootstrap.Offcanvasbootstrap.Drawer
    • Events: show.bs.offcanvasshow.bs.drawer, etc.
    • CSS variables: --offcanvas-*--drawer-*
    • Sass: $zindex-offcanvas$zindex-drawer
  • New .drawer-sheet variant for flush-to-edge panels with no inset, border-radius, or shadow.
  • Swipe-to-dismiss gesture support on touch devices for Drawer components. Drawers automatically detect their placement and dismiss on the appropriate swipe direction.
  • Dialog and Drawer share DialogBase — the show/hide/toggle lifecycle, keyboard handling, backdrop clicks, and static backdrop bounce are consolidated in a shared base class.
  • Reworked button variants. The v5 per-color classes like .btn-primary, .btn-outline-primary, .btn-secondary, etc. are replaced by a composition of variant + theme classes:
    • .btn-primary.btn-solid .theme-primary
    • .btn-outline-primary.btn-outline .theme-primary
    • New variant classes: .btn-solid, .btn-outline, .btn-subtle, .btn-text, plus .btn-styled for gradient/shadow depth and .btn-link for link-style buttons.
    • Color is applied via .theme-* utility classes (e.g., .theme-primary, .theme-danger, .theme-success) rather than being baked into each button class.
    • Shared sizing tokens: Buttons and inputs now share --btn-input-* CSS variables for consistent sizing.
    • New .btn-icon class for square icon-only buttons with aspect-ratio: 1.
    • Added xs button size (.btn-xs).
  • Rebuilt accordion on native <details> / <summary>. The markup structure has fundamentally changed:
    • v5: .accordion-item > .accordion-header > button.accordion-button + .accordion-collapse > .accordion-body, controlled by the Collapse JavaScript plugin.
    • v6: <details class="accordion-item"> > <summary class="accordion-header"> + <div class="accordion-body">, using the browser’s native disclosure widget. No JavaScript dependency for basic open/close behavior.
    • Exclusive accordion groups (only one item open) are handled via the HTML name attribute on <details> elements, replacing the data-bs-parent approach.
    • The .accordion-button and .accordion-collapse classes have been removed.
    • The expand/collapse icon now uses an .accordion-icon element (typically an SVG) inside the summary, replacing the CSS background-image approach.
    • Open state styling uses details[open] instead of JavaScript-toggled classes.
    • Theme coloring via .theme-* classes on the .accordion wrapper.
  • Rebuilt close button markup. .btn-close now renders its icon via a CSS mask-image (--btn-close-icon) tinted with background-color: currentcolor, so the button is self-contained—no child <svg> is required. The filter-based dark mode approach ($btn-close-white-filter) has been replaced by currentcolor inheritance, and the .btn-close-white class has been removed—on dark backgrounds (e.g., .text-bg-dark) the icon now inherits the contrast color automatically.
  • Removed .alert-dismissible. Dismissible alerts no longer require the .alert-dismissible modifier class. Place a .btn-close directly inside the alert—the alert's flex layout positions it automatically. Remove any .alert-dismissible class from your markup.
  • Restructured cards. Borders now live on .card-body and .card-list segments rather than a single outer .card border. Added --card-box-shadow and --card-body-gap tokens. New variant classes: .card-translucent (frosted glass effect) and .card-subtle (themed with subtle backgrounds). Horizontal cards use a new .card-row class. Removed .card-link class.
  • Card groups now use container queries. .card-group switches to its attached, equal-width row layout with a @container query instead of a viewport @media query, so it responds to the width of a parent query container rather than the viewport. Wrap the card group in a query container—e.g. add the .contains-inline utility to a parent element—or the cards stay stacked.
  • List group horizontal variants now use container queries. The .*:list-group-horizontal classes switch between vertical and horizontal layouts with @container queries instead of viewport @media queries, responding to a parent query container rather than the viewport. Wrap the list group in a query container (e.g. .contains-inline) for the responsive variants to take effect.
  • Reworked badge variants. Badge color variants now use .badge-subtle and .badge-outline combined with .theme-* classes (e.g., .badge-subtle .theme-primary), replacing the v5 .bg-primary utility pattern on badges.
  • Updated breadcrumb markup. Breadcrumbs now use .breadcrumb-link as an interactive element with padding, min-height, and hover background, and explicit .breadcrumb-divider elements as separators between items. An empty .breadcrumb-divider renders a default chevron via a CSS mask-image (--breadcrumb-divider-icon) tinted with background-color: currentcolor; add your own SVG, text, or markup inside it to override. This replaces the v5 --bs-breadcrumb-divider content string on the .breadcrumb-item::before pseudo-element.
  • Navbar toggler icon now uses a CSS mask. .navbar-toggler-icon renders via mask-image (--navbar-toggler-icon) tinted with background-color: currentcolor instead of an embedded background-image SVG. The markup stays an empty <span class="navbar-toggler-icon">, but the icon now inherits the current text color (including dark mode), so the separate light/dark toggler SVGs are no longer needed.
  • Rebuilt the carousel on CSS scroll snap. The slide engine no longer uses float + translateX class juggling or a custom swipe handler—.carousel-inner is now a native horizontal scroll-snap container, so sliding, touch dragging, momentum, and keyboard scrolling come from the browser. The markup is unchanged (.carousel.carousel-inner.carousel-item), and the public JavaScript API (next, prev, to, cycle, pause) plus the slide.bs.carousel / slid.bs.carousel events are preserved.
    • New capabilities: show multiple slides at once, reveal a “peek” of adjacent slides, gaps, center mode, and variable-width slides—all via CSS custom properties (--carousel-items, --carousel-items-gap, --carousel-items-peek) and the .carousel-center / .carousel-auto variants.
    • Removed transitional classes .carousel-item-start, .carousel-item-end, .carousel-item-next, and .carousel-item-prev, plus the .carousel.pointer-event helper—the browser tracks scroll position instead of an .active layout class. (The active slide still gets .active for styling.)
    • .carousel-fade is now a stacked-opacity (grid) crossfade animated with a CSS opacity transition over --bs-carousel-fade-duration (it collapses to an instant swap under reduced motion).
    • Removed the touch option. Because .carousel-inner is a native scroll-snap container, horizontal touch dragging is part of the browser’s native scrolling and is no longer toggled by JavaScript.
    • Active-slide syncing uses an IntersectionObserver; as a result slid.bs.carousel fires when the new slide settles into view, and a multi-slide to() jump may emit intermediate slid events as it scrolls past.
  • Renamed the carousel ride option to autoplay, and made it a boolean. Autoplay is now strictly opt-in: a carousel only autoplays when autoplay is true (set via data-bs-autoplay="true"). The old ride option and its string "carousel" value have been removed, as has the ride="true" behavior that started autoplaying only after the first user interaction.
    • data-bs-ride="carousel"data-bs-autoplay="true"
    • data-bs-ride="true"data-bs-autoplay="true" (it now autoplays on load like any other autoplaying carousel, instead of waiting for the first interaction)
    • JavaScript: new bootstrap.Carousel(el, { ride: 'carousel' })new bootstrap.Carousel(el, { autoplay: true })
    • The auto-initialization-on-load selector changed accordingly from [data-bs-ride="carousel"] to [data-bs-autoplay="true"].
  • Autoplaying carousels now stop when the user interacts with them. Clicking a control or indicator, navigating with the keyboard, or swiping permanently stops autoplay instead of resuming it, respecting the visitor’s intent (WCAG 2.2.2). Previously these interactions kept the carousel cycling.
  • New .carousel-control-play-pause control provides a discoverable, accessible button to pause and resume an autoplaying carousel—the mechanism WCAG 2.2.2 requires (a hover-only pause does not qualify). It renders pause/play icons via CSS masks (--carousel-control-pause-icon / --carousel-control-play-icon), swaps its icon and aria-label with state, and can also start autoplay on an otherwise static carousel.
  • Stacked is now the default carousel layout. .carousel is a flex column, so prev/next controls, indicators, and any custom content sit in the flow above or below the slides. The old .carousel-stacked class was removed—it’s now the default, so drop it from your markup.
  • Overlaid controls now require the .carousel-overlay modifier. To overlay the prev/next controls, play/pause button, and indicators on top of the slides (the classic v5 look), add .carousel-overlay to the .carousel element. Without it, those elements lay out in the flow.
  • Renamed the control-icon classes .carousel-control-prev-icon.carousel-icon-prev and .carousel-control-next-icon.carousel-icon-next. The icons are now painted with background-color: currentcolor, so they inherit the surrounding text color (white on the overlay controls, the button color inside .btn-*). Size them with --bs-carousel-control-icon-width.
  • Removed .carousel-caption and its --carousel-caption-* tokens. Compose slide content from your own markup inside .carousel-item instead, styling and positioning it with utilities or custom CSS.
  • Replaced the wrap option with ends. v5's wrap: true | false is now ends: "loop" | "wrap" | "stop" (default "loop"). Map wrap: true to ends: "wrap" (or the new default "loop" for the seamless conveyor effect) and wrap: false to ends: "stop". Set it via data-bs-ends or the ends option. Unknown values fall back to "loop".
  • Removed the .carousel-control-prev / .carousel-control-next button classes. The absolute, full-height hover targets are gone. Compose a control from a button (e.g. .btn-icon) plus data-bs-slide="prev" / data-bs-slide="next" and a .carousel-icon-prev / .carousel-icon-next glyph, placed in the flow or inside .carousel-overlay-controls.
  • Removed the .carousel-dark class. Use data-bs-theme="dark" on the .carousel (typically alongside .carousel-overlay) for reversed contrast.
  • Removed v5 carousel tokens that no longer have an equivalent: --carousel-caption-*, --carousel-control-color, --carousel-control-opacity / --carousel-control-hover-opacity, --carousel-control-icon-filter, and --carousel-transition. Indicator, control, and fade styling now derive from the redesigned --bs-carousel-* tokens listed under CSS variables.

Reboot

  • Relocated heading classes (like .h1) and some type classes (.mark and .small) to Reboot from _type.scss. This avoids a dependency in Sass modules and we like to avoid extending selectors in general.
  • Removed the ::-moz-focus-inner styles, as the pseudo selector is deprecated in Firefox.
  • Removed text-transform: none from button and select elements, as Firefox no longer incorrectly inherits text-transform.
  • Added accent-color: var(--primary-base) on :root, which applies the primary theme color to native form controls (checkboxes, radios, range inputs, progress bars) globally.

Forms

  • Refactor checks, radios, and switches.
    • Split apart _form-check.scss into separate stylesheets: _checkbox.scss, _radio.scss, and _switch.scss.
    • Also split apart the documentation pages for checks, radios, and switches.
    • Added new CSS variables on each of these components. Side note: we could’ve shared variables here, but chose not to for simplicity’s sake.
    • Removed several now unused Sass variables.
    • Checkboxes apply the .check class directly on the <input> (no wrapping element) and draw the checked and indeterminate marks with a CSS mask-image on a ::before pseudo-element tinted with the contrast color—no inline SVG in the DOM. Radios and switches are likewise styled without a wrapping element.
    • Revamped layout for checks, radios, and switches with labels (and descriptions). We now have custom elements for layout that include basic flexbox styling.
    • Refactored toggle buttons to use a nested input structure. The .btn-check class now goes on the label (not the input), with the input nested inside. This eliminates the need for id/for attributes and uses CSS :has() selector instead of sibling selectors. Example: <label class="btn-check btn-solid theme-primary"><input type="checkbox">Toggle</label>.
  • Consolidate .form-select into .form-control.
    • Removed .form-select—use .form-control on <select> elements now. Too much abstraction and duplication at the same time.
    • Adds new CSS variables on .form-control for easier customization without Sass compilation.
    • .form-control now has a min-height at all times as opposed to just on <textarea> elements. This reduces some CSS for us.
  • Added new Combobox form component. A searchable, filterable select with single and multi-select modes, built on top of the Menu component. See the Combobox docs.
  • New .form-field layout component. Replaces .checkgroup and .radiogroup wrappers with a unified grid-based layout primitive for label + control + help text + validation feedback. Use .form-field, .form-field-content, and .form-field-card for structured form layouts, and .form-group for grouping related fields. See the Field docs.
  • Overhauled form validation.
    • Client-side validation no longer uses .was-validated or bare :valid / :invalid pseudo-classes. Instead, add data-bs-validate to your <form> to opt in to :user-invalid styling that activates only after user interaction. To also show success styling, use data-bs-validate="valid".
    • Server-side validation with .is-invalid / .is-valid classes is unchanged and works globally without data-bs-validate.
    • Custom validation states (e.g., "warning") use only .is-* classes—no pseudo-class support.
    • Validation feedback tooltips now require both .tooltip and .valid-tooltip / .invalid-tooltip classes (e.g., <div class="tooltip invalid-tooltip">).
    • Removed $enable-validation-icons and all built-in validation background icons on form controls.
    • The $form-validation-states Sass map is replaced by $validation-states, a simpler map pairing state names to theme keys (e.g., "invalid": "danger"). Styling uses theme-derived CSS custom properties (--danger-fg, --danger-border, etc.) instead of per-state Sass variables.
    • The form-validation-state mixin signature changed from ($state, $color, $icon, ...) to ($state, $theme).
    • Renamed scss/mixins/_forms.scss to scss/mixins/_form-validation.scss. The mixin now only contains the selector logic; the full state mixin lives in scss/forms/_validation.scss.
    • Removed scss/forms/_form-variables.scss (feedback variables and icon SVG data URIs).
    • Validation JS: replace document.querySelectorAll('.needs-validation') with document.querySelectorAll('form[data-bs-validate]') and remove the form.classList.add('was-validated') line.
  • Simplified input groups.
    • Removed .has-validation—border-radius logic no longer branches on it.
    • Added .input-group-ignore for elements that should be skipped by adjoined border-radius rules.
    • Removed flex-wrap: wrap from .input-group; validation feedback now lives outside the input-group in a parent .form-field.
  • Updated form label defaults. .form-label and .col-form-label now default to font-weight: 500 (was inherit) and font-size: inherit (was var(--font-size-sm)).
  • Updated form helper text. .form-text color changed from var(--fg-3) to var(--fg-2). The margin-top from --form-text-margin-top is removed; spacing is handled by the parent .form-field grid gap.
  • Reworked switch internals. The switch thumb is now absolutely positioned with inset-inline-start transitions instead of padding-based animation. New tokens: --switch-indicator-width, --switch-indicator-height. Themes overriding old padding-based switch behavior will need updating.

Helpers

  • Ratio helpers have been moved to utilities.
  • Dropped clearfix helper for .d-flow-root utility.

Utilities

  • Expanded spacer scale. The $spacers map has been expanded from 6 steps (0–5) to 10 steps (0–9) with finer granularity. Note that spacer keys no longer map to the same values as v5:
Keyv5 valuev6 value
000
10.25rem0.25rem
20.5rem0.5rem
31rem0.75rem (new)
41.5rem1rem (was key 3)
53rem1.25rem (new)
61.5rem (was key 4)
72rem (new)
82.5rem (new)
93rem (was key 5)
  • New fixed-size scale. A new $sizes map (keys 1–12) provides fixed rem-based widths, merged into the width utility. v5 had no equivalent fixed-size scale in $sizes—it only had percentage-based values.
Keyv5 $sizesv6 $sizes
11rem
22rem
33rem
44rem
55rem
66rem
77rem
88rem
99rem
1010rem
1111rem
1212rem
2525%25% (in width utility)
5050%50% (in width utility)
7575%75% (in width utility)
100100%100% (in width utility)
autoautoauto (in width utility)
  • Font size scale reworked. .fs-1 through .fs-6 (numeric, descending size) have been replaced by t-shirt size keys from .fs-xs through .fs-6xl (10 steps, ascending). Larger sizes use clamp() for responsive scaling. New .text-{size} utilities set both font-size and line-height together.
v5 classv5 valuev6 classv6 value
.fs-61rem.fs-md1rem
.fs-51.25rem.fs-lgclamp(1.25rem, …, 1.5rem)
.fs-41.5rem.fs-xlclamp(1.5rem, …, 1.75rem)
.fs-31.75rem.fs-2xlclamp(1.75rem, …, 2rem)
.fs-22rem.fs-3xlclamp(2rem, …, 2.5rem)
.fs-12.5rem.fs-4xlclamp(2.25rem, …, 3rem)
.fs-xs0.75rem
.fs-sm0.875rem
.fs-5xlclamp(3rem, …, 4rem)
.fs-6xlclamp(3.75rem, …, 5rem)
  • Border radius tokens replaced with a numeric scale. The named $border-radius-* Sass variables and --border-radius-* CSS custom properties (-xs, -sm, default, -lg, -xl, -2xl) have been removed in favor of a numeric $radii Sass map (keyed 09) that generates --radius-0 through --radius-9 tokens, plus --radius-pill. The scale is driven by a single $radius: .5rem base, so all steps move together when the base changes. To migrate any custom Sass or CSS that referenced the old tokens directly:
v5 variablev5 valuev6 tokenv6 value
$border-radius-xs / --border-radius-xs0.25rem--radius-30.25rem
$border-radius-sm / --border-radius-sm0.25rem--radius-30.25rem
$border-radius / --border-radius0.375rem--radius-40.375rem
$border-radius-lg / --border-radius-lg0.5rem--radius-50.5rem
$border-radius-xl / --border-radius-xl1rem--radius-81rem
$border-radius-xxl / --border-radius-2xl2rem— (closest: --radius-9 1.5rem)
$border-radius-pill / --border-radius-pill50rem--radius-pill50rem
  • Border radius utilities expanded and remapped. .rounded-* is now generated from the $radii map, so the scale spans .rounded-0 through .rounded-9 (previously .rounded-0 through .rounded-5). The default .rounded still resolves to 0.5rem, but the numbered classes now map to different values than v5:
Utility classv5 valuev6 valuev6 token
.rounded0.375rem0.5remvar(--radius-5)
.rounded-000var(--radius-0)
.rounded-10.25rem0.125remvar(--radius-1)
.rounded-20.375rem0.1875remvar(--radius-2)
.rounded-30.5rem0.25remvar(--radius-3)
.rounded-41rem0.375remvar(--radius-4)
.rounded-52rem0.5remvar(--radius-5)
.rounded-60.625remvar(--radius-6)
.rounded-70.75remvar(--radius-7)
.rounded-81remvar(--radius-8)
.rounded-91.5remvar(--radius-9)
.rounded-circle50%50%
.rounded-pill50rem50remvar(--radius-pill)

To preserve v5 visual roundness, shift class numbers up the scale (e.g. .rounded-1.rounded-3, .rounded-2.rounded-4, .rounded-3.rounded-5, .rounded-4.rounded-8, .rounded-5.rounded-9). The .rounded-{top,end,bottom,start}-* directional variants follow the same scale.

  • Font weight additions. Added .fw-medium (500) and .fw-semibold (600) utilities. v5 only had lighter, light (300), normal (400), bold (700), and bolder.
  • Negative margins limited. Negative spacers are reduced to only -1 (-0.25rem) and -2 (-0.5rem), and only applied to margin-inline-start (.ms-n1, .ms-n2) and margin-inline-end (.me--1, .me--2). The v5 full negative margin utilities across all sides have been removed.
  • Spacing and border utilities now use CSS logical properties. margin-topmargin-block-start, margin-rightmargin-inline-end, padding-leftpadding-inline-start, border-rightborder-inline-end, etc. Class names (.mt-*, .me-*, .ps-*, .border-end) remain the same, but the underlying CSS properties are now logical, improving RTL and writing-mode support.
  • Text wrap additions. Added .text-balance and .text-pretty values to the text-wrap utility.
  • Color utility renames. .text-* color utilities have been replaced by .fg-* (foreground) utilities. New .fg-emphasis-* and .fg-contrast-* variants. Background utilities now include .bg-subtle-* and .bg-muted-* in addition to .bg-*. Added .fg-bg and .bg-fg cross-reference utilities; removed .fg-inherit and .bg-inherit. Renamed .bg-opacity-* to .bg-*. Renamed .text-reset to .fg-reset.
  • Display utilities: added flow-root and contents options.
  • Sizing utilities:
    • Renamed .mh-*/.mw-* to .max-h-*/.max-w-*
    • Added .min-h-* and .min-w-* utilities with two default values, 0 and 100%
    • Added auto, min-content, max-content, and fit-content to width and height utilities.
  • Flex & Grid utilities:
    • Added .place-items and .justify-items utilities.
    • Added .grid-cols-* utilities for grid-template-columns (1–4 and 6 column layouts), .grid-cols-fill for spanning all columns, and .grid-auto-flow utility.
  • Container query utilities. New .contains-inline and .contains-size utilities for container-type.
  • Ratio helpers are now powered by the utility API and use simplified values without calc().
  • State variants now use prefix syntax. Pseudo-state utility classes like hover and focus variants now use a state:class prefix pattern (e.g., hover:opacity-50 instead of opacity-50-hover), matching the responsive prefix convention.
  • Utility API cleanup. Removed css-var, css-variable-name, and local-vars options from the utility API. Use the property map approach for CSS custom properties and variables for static CSS custom properties within utility classes.

New components and plugins

  • Dialog — replaces Modal, built on the native <dialog> element. See the Components section above for the full migration.
  • Stepper — new .stepper component for multi-step workflows with .stepper-item and .stepper-horizontal variant. CSS-only.
  • Avatar — new .avatar component with sizes (.avatar-xs through .avatar-xl), status indicators (.avatar-status .status-online|offline|busy|away), subtle variant, and .avatar-stack for grouped avatars.
  • Chip and Chip Input — new .chip component for tags/tokens and .chip-input for interactive chip entry. Chips JavaScript plugin with events: add.bs.chips, remove.bs.chips, change.bs.chips.
  • OTP Input — new .otp component for one-time password fields. Built on a single <input> rendered as separate digit slots for full accessibility. OtpInput JavaScript plugin with events: input.bs.otpInput, complete.bs.otpInput (both expose event.value).
  • Password StrengthStrength JavaScript plugin for password strength metering with strengthChange.bs.strength event.
  • TogglerToggler JavaScript plugin for toggling classes or attributes on elements via data-bs-toggle="toggler".
  • DatepickerDatepicker JavaScript plugin built on Vanilla Calendar Pro, with events: change.bs.datepicker, show.bs.datepicker, hide.bs.datepicker.
  • Form Adorn — new .form-adorn component for adding icons or text decoration to form inputs.
  • Prose — new .prose class for rich typography scoping and .not-prose to opt out of prose styles within a prose container.
  • NavOverflowNavOverflow JavaScript plugin for handling overflowing navigation items.
  • Submenu — nested menu support via .submenu class within Menu, with submenuTrigger (hover, click, or both) and submenuDelay options.

Docs

  • Removed all AddedIn badges.
  • Rearranged utilities documentation to break apart larger pages that included groups of utilities. Sizing, spacing, flex, type, and more have been broken out into smaller pages with new sub-group headings in the sidebar.