On this page
Bootstrap HTML
Bootstrap 5.3.8 look and tokens, driven by native HTML instead of
bootstrap.js. The widgets are usable as soon as the
HTML is parsed.
What you get
23.3 KB not shipped
bootstrap.bundle.min.js is 23.3 KB gzip.
That one file is every plugin, including Popper. A native
widget on this site is 0 KB of component script. Per-plugin
figures on each page overlap, so they are not a sum.
Ready before script
A dialog, disclosure, or popover works when the parser reaches
it. Bootstrap’s plugins do nothing until bootstrap.js
has downloaded, parsed, and attached listeners. That main-thread
work is
Total Blocking Time
(TBT): long tasks between first paint and interactive. These
controls add no such task, and a click does not wait for it.
0.3 KB, then only gaps
Every page loads the polyfill loader, 0.3 KB gzip. It checks the APIs and stops. The other modules are 4.9 KB gzip together, and each one downloads only when that API is missing and the page uses it. As browsers ship the features, those files stop being requested. Sizes and conditions.
2 widgets still need a small script, and only on pages that use them: alerts (0.5 KB gzip) and tabs (1.3 KB gzip). The optional behaviors — pe-focus-group, pe-toast and pe-carousel — stay unloaded until you import them.
Native HTML and CSS in a current browser. No component script. Script required for the behavior. Not a polyfill. Polyfill loader and chunks for missing APIs
Browser support
Three columns, not one baseline. Anchor positioning has no polyfill: a dropdown or popover still opens, but it is not tethered to the button in Firefox before 147 or Safari before 26.
| Component | No script | Polyfill | No fallback |
|---|---|---|---|
| Accordion | details / summary. Exclusive name: Chrome 120+, Firefox 130+, Safari 17.2+. | None | The height transition needs interpolate-size: allow-keywords to animate block-size from 0 to auto. That is Chromium-only (Chrome 129+), so Firefox and Safari snap the panel open. ::details-content itself is Chrome 131+, Firefox 143+, Safari 18.4+, and only styles the panel. |
| Alerts | A static .alert needs no script. Without alert.js a dismissible alert stays. | command / commandfor when that API is missing. pe-alert is still required to remove the node. | No native dismiss for an inline alert. |
| Buttons | .btn-check is CSS on a native checkbox or radio. Chrome, Firefox, and Safari have shipped it with Bootstrap’s stylesheet. | None | None. Without this stylesheet the control is still a checkbox or radio. The label just looks like a button when .btn-check is present. |
| Carousel Experimental | scroll-snap-type is widely available. Swipe works. | None | ::scroll-button and ::scroll-marker: Chrome 135+ only. Optional pe-carousel covers buttons and pips elsewhere. No autoplay, fade, or wrap. |
| Collapse | details / summary. Trigger and panel must be parent/child. | None | Same interpolate-size limit as the accordion. .collapse-horizontal animates to max-content and depends on it too. The panel opens and closes everywhere. |
| Dropdown | popover="auto": Chrome 114+, Firefox 125+, Safari 17+. | command on the toggle, the same polyfill as the modal. | CSS Anchor Positioning: Chrome 125+, Safari 26+, Firefox 147+. Without it the menu is not attached to the button. There is no Popper fallback. |
| Modal | dialog | command / commandfor (Chrome 135+, Firefox 144+, Safari 26.2+). closedby is missing in Safari. | None |
| Navbar | The same details menu. .navbar-expand-* shows it from that breakpoint. The offcanvas variant is a dialog. | The offcanvas toggler uses the same command polyfill as the modal. | The hamburger cannot sit outside the <details>. Use the offcanvas navbar for that. |
| Offcanvas | dialog with .offcanvas-start, -end, -top, or -bottom. | The same command and closedby polyfills as the modal. | None |
| Popover | popover="auto": Chrome 114+, Firefox 125+, Safari 17+. | command on the trigger, the same polyfill as the modal. | CSS Anchor Positioning: Chrome 125+, Safari 26+, Firefox 147+. Without it the bubble is not attached to the trigger. .bs-popover-auto does not reorient; it matches .bs-popover-end. |
| Progress | <progress> is Baseline Widely available (2015). <meter> is too (2017). The fill follows value / max with no script. | None | Without this CSS the native control still works. Token colors, radius, and the striped animation need appearance: none and the vendor bar pseudos. |
| Scrollspy Experimental | scroll-target-group: Chrome 140+. | IntersectionObserver when the CSS is missing. It does not set aria-current. | Marked experimental on the component page. The polyfill covers browsers without the CSS. |
| Spinner | aria-busy is a global ARIA state. The disc is CSS on [aria-busy="true"]. Removing the attribute hides it. | None | Without this CSS the attribute still tells assistive technology. There is no disc. .spinner-border stays in the sheet as a standalone glyph. |
| Tabs | The .active pane stays visible. Other tabs do not switch. | None. pe-tabs is required to switch panes. It is not a polyfill. | No native tab control yet. Dropdowns inside a tab list are not supported. |
| Toasts | popover="manual" with command to show and close. No light-dismiss. | None for show and close. Optional pe-toast for data-delay and stacking. | Without pe-toast, open toasts share one corner. |
| Tooltip | aria-description and content: attr(). Hover and focus. Plain text, not in the top layer. | interestfor when interestForElement is missing and the page has [interestfor]. It does not polyfill popover="hint". | popover="hint" is Chrome 151+, Firefox 153+, Safari preview. Without hint the polyfill cannot open the top-layer bubble. .bs-tooltip-auto on a hint popover flips with position-try-fallbacks. The arrow stays in the default (top) orientation after a flip. |
| Composed examples Experimental | Drawer, context menu, and the progress ring work without a component script. The multi-select menu opens without script. <output data-controls> needs src/behaviors/count.js to stay in sync, and a closed popover cannot increment a CSS counter. command still uses the invoker polyfill when missing. | The same command polyfill as dropdown and offcanvas. | Context-menu position-area and the tab underline need CSS Anchor Positioning. The shrinking header and scroll bar need animation-timeline: scroll() (not in Firefox). The ring’s attr() into --bs-progress-value is Chrome 133+; set the custom property in markup elsewhere. No scroll-listener and no pointer-coordinate script. src/behaviors/count.js is the one optional script on this page that is not a pe-* behavior. |
| Customizable select Experimental | appearance: base-select: Chrome 135+, Safari 27+. Firefox 149 behind dom.select.customizable_select.enabled and layout.css.appearance-base.enabled. | None | Marked experimental on the forms page. Without base-select the closed .form-select and the OS picker stay. Rich markup inside <option> is stripped to text. <button> / <selectedcontent> inside the select are ignored. |
| Datalist | <datalist>: Chrome 20+, Firefox 4+, Safari 12.1+. Firefox on Android does not show the suggestion menu. | None | None. Without a suggestion menu the input still accepts a typed value. |
| Field sizing | field-sizing: content: Chrome 123+, Firefox 152+, Safari 26.2+. | None | None. Without field-sizing the control keeps Bootstrap’s fixed box. |
| Output | <output> is Baseline Widely available. Without count.js the text stays at whatever was in the markup. | None | A closed popover is display: none, so a CSS counter inside that menu stays 0. count.js is the exception for that case. Visible checkboxes can use :has() and counter() without it. |
| Range group | Two native type="range" on one track. Keyboard, submit, and focus work. Without range-group.js thumbs can cross and there is no fill. data-range-group="off" skips the module when it is loaded. | None | The track is not a click target: pointer-events: none on the inputs, so only the thumbs receive pointer events. The later thumb in the DOM wins when they overlap. Fill needs range-group.js. |
| Spin button | The input still accepts a typed or native-stepped value. Without step.js the plus and minus buttons do nothing. | command / commandfor when that API is missing. step.js is still required to change the value. | stepUp() throws on step="any" and on types that have no step. An empty date, time, week, or month field without min or value jumps to 1970-01-01 or 00:00. |
| Validation | :user-valid / :user-invalid: Chrome 119+, Firefox 88+, Safari 16.5+. | None | None |