Skip to content

Documentation

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>.

What replaces Bootstrap’s JavaScript hooks. Plugin options this package never shipped are unchanged: keep your own script.
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