On this page
Getting started
Bootstrap 5.3.8 look on native HTML. One stylesheet, and a polyfill module that stays idle when the browser already has the API.
Stylesheet
Install bootstrap-html and point a link at the compiled CSS.
The polyfill script is a module URL so it can still fetch a fallback
next to itself. A bundler that inlines that file will break the
feature checks.
<link rel="stylesheet" href="./node_modules/bootstrap-html/dist/css/bootstrap-html.min.css">
<script type="module" src="./node_modules/bootstrap-html/src/polyfills/index.js"></script>
The loader checks commandForElement, closedBy,
scroll-target-group, and interestForElement.
It downloads a module only when that API is missing and the page has
matching markup. See
Polyfills.
Sass
bootstrap stays installed. Set variables after Bootstrap’s
functions and before this entry. Those variables are
!default, so a value you set first is the one that ships.
@import "bootstrap/scss/functions";
$primary: #6610f2;
@import "bootstrap-html/scss/bootstrap-html";
On the compiled CSS, skip Sass and set --bs-* custom
properties on a parent.
Migration
Triggers are <button> elements.
command does not work on <a href>.
| Bootstrap JS | Bootstrap HTML | Component |
|---|---|---|
| Accordion buttons | <details class="accordion-item"> with <summary class="accordion-button"> | Accordion |
data-bs-parent | The same name on every <details> in the group | Accordion |
data-bs-dismiss="alert" | <pe-alert class="alert"> with command="--dismiss" and commandfor set to the alert id. Load src/behaviors/alert.js. | Alerts |
data-bs-toggle="button" and aria-pressed set from script | <input type="checkbox" class="btn-check"> or type="radio" with a <label class="btn"> | Buttons |
data-bs-toggle="collapse" | <details> / <summary>. The trigger must wrap the panel. | Collapse |
data-bs-toggle="modal" and data-bs-target | command="show-modal" and commandfor on a button. The dialog uses closedby. | Modal |
data-bs-dismiss="modal" | command="close" and the same commandfor | Modal |
| A confirm or prompt plugin, or click listeners on footer buttons | <form method="dialog"> inside the dialog. Submit buttons set dialog.returnValue to their value and close without script. | Modal |
data-bs-backdrop="static" | closedby="closerequest" on the <dialog> | Modal |
data-bs-toggle="offcanvas" | The same commands on <dialog class="offcanvas"> | Offcanvas |
data-bs-scroll="true" | popover="manual" instead of a modal <dialog> | Offcanvas |
data-bs-toggle="collapse" on .navbar-toggler | <summary class="navbar-toggler"> inside <details class="navbar-collapse"> | Navbar |
data-bs-toggle="dropdown" | command="toggle-popover" and popover="auto" on .dropdown-menu | Dropdown |
data-bs-offset and data-bs-reference | anchor() and position-try-fallbacks in CSS | Dropdown |
| Submenus (Bootstrap 5 dropped them; Popper could not keep the parent open) | .dropdown-submenu with a nested popover="auto" inside the parent menu | Dropdown |
data-bs-toggle="popover" | popover="auto" on the bubble, opened with command="toggle-popover" | Popover |
data-bs-content and data-bs-placement | Real markup inside .popover-body, and .bs-popover-top / -end / -bottom / -start | Popover |
title / data-bs-title | aria-description plus .bs-tooltip-top (or end, bottom, start), or a .tooltip with popover="hint" | Tooltip |
data-bs-html | Real markup in .tooltip-inner on a hint popover | Tooltip |
data-bs-delay | interest-delay-start / interest-delay-end and --interest-delay-start / --interest-delay-end (or --bs-tooltip-delay-start / --bs-tooltip-delay-end) on the trigger | Tooltip |
data-bs-spy="scroll" and data-bs-target | data-polyfill-scrollspy on the link group, which applies scroll-target-group: auto. :target-current marks the current link. | Scrollspy |
data-bs-toggle="tab" and data-bs-target | <pe-tabs> with role="tab" and aria-controls. Load src/behaviors/tabs.js. | Tabs |
data-bs-autohide and data-bs-delay | data-delay on <pe-toast>. Load src/behaviors/toast.js. | Toasts |
data-bs-dismiss="toast" | command="hide-popover" and commandfor | Toasts |
data-bs-ride and data-bs-slide | ::scroll-button() and ::scroll-marker in Chromium, or command="--prev" / --next / --slide on <pe-carousel> | Carousel |
.carousel-control-prev-icon markup | content on ::scroll-button() | Carousel |
<div class="progress"><div class="progress-bar" style="width: 25%"> | <progress class="progress" value="25" max="100">25%</progress> | Progress |
role="progressbar" and aria-valuenow / min / max | The value and max attributes. Do not add a role. | Progress |
.progress-bar.bg-success | .text-success on the <progress>. The fill is currentcolor. | Progress |
<span class="spinner-border" role="status"><span class="visually-hidden">Loading…</span></span> | aria-busy="true" on the host that is loading | Spinner |
A .spinner-border child inside a disabled button | aria-busy="true" and disabled on the button. The name stays. | Spinner |
.was-validated | :user-valid / :user-invalid. No wrapper class. | Validation |
| The submit listener that adds it | Nothing. The browser decides when the user has interacted. | Validation |
Autosize scripts that grow a textarea on input | .field-sizing on .form-control or .form-select | Field sizing |
| A dropdown or typeahead plugin wired to an input | list on the input pointing at a <datalist> id | Datalist |
| A span or badge rewritten from a change listener you wrote | <output data-controls> with the container id, no hash, and src/behaviors/count.js | Output |
role="spinbutton" and aria-valuenow / min / max on an <input type="text"> | A native <input type="number"> (or date, time, range). min, max, and step are the constraints. | Spin button |
aria-controls and data-* increment buttons wired in page script | command="--step-up" / --step-down and commandfor set to the input id. Load src/behaviors/step.js. | Spin button |
noUiSlider, or a custom role="slider" multi-thumb widget | Two <input type="range" class="form-range"> in .form-range-track, inside <fieldset class="form-range-group"> | Range group |
aria-valuemin / aria-valuemax updated as the other thumb moves | Value clamp in src/behaviors/range-group.js. Do not rewrite min / max on the inputs; that breaks the shared track. | Range group |
| A dropdown or Choices/Select2 widget used as a select | <select class="form-select">. ::picker(select) is the menu. | Customizable select |
A context-menu plugin that measures clientX / clientY | popover="auto" with .dropdown-menu-context and position-area on .context-host | Composed examples |
| A JS drawer that scroll-snaps nested panels | <dialog class="offcanvas"> with nested <details> in .nav-tree | Composed examples |
<select multiple> restyled as a dropdown | .dropdown-multiselect with .form-check-input in the menu | Composed examples |
.show | :open on a dialog, :popover-open on a popover, [open] on <details> | All |
bootstrap.bundle.min.js | Nothing for these components. src/polyfills/index.js loads a fallback only when an API is missing. | All |