Skip to content

Documentation

On this page

Polyfills

Native HTML is the default. When a browser is missing an API this site actually uses, a matching module is loaded — nothing more.

How loading works

  • BaseLayout imports src/polyfills/index.js once. That file is the only top-level await entry (Safari mis-handles several modules importing the same awaiting file at once).
  • Each feature is detected on the prototype or with CSS.supports — not on window.
  • A module is fetched only if the feature is missing and the document contains markup that needs it ([commandfor], dialog[closedby], [data-polyfill-scrollspy], or [interestfor]).
  • Browsers with full support download the loader, run four cheap checks, and stop. No extra chunks, no listeners.

A browser that already has the APIs downloads only the loader, 0.3 KB gzip. The other modules stay on the server.

Minified and gzip. For scale, Bootstrap’s bootstrap.bundle.min.js is 23.3 KB gzip.
Module When it loads JavaScript
index.js Always. A browser that already has the APIs stops here. 0.3 KB gzip
invoker-commands.js commandForElement is missing and the page has [commandfor]. 0.8 KB gzip
dialog-closedby.js closedBy is missing and the page has dialog[closedby]. 0.5 KB gzip
scroll-target-group.js scroll-target-group is missing and the page has [data-polyfill-scrollspy]. 0.8 KB gzip
interestfor.js interestForElement is missing and the page has [interestfor]. 2.8 KB gzip
/**
 * Site-wide polyfill loader.
 *
 * Feature-detect each API and dynamic-import only the missing modules, and
 * only when the current document actually uses that API. Browsers with full
 * support download this file and make a few cheap checks — no extra chunks,
 * no listeners. Import this module once from the document (Safari: do not
 * import a top-level-await module from multiple sibling scripts at once).
 */

const polyfills = [];

if (
  !('commandForElement' in HTMLButtonElement.prototype) &&
  document.querySelector('[commandfor]')
) {
  polyfills.push(import('./invoker-commands.js'));
}

if (
  !('closedBy' in HTMLDialogElement.prototype) &&
  document.querySelector('dialog[closedby]')
) {
  polyfills.push(import('./dialog-closedby.js'));
}

if (
  (typeof CSS === 'undefined' || !CSS.supports('scroll-target-group: auto')) &&
  document.querySelector('[data-polyfill-scrollspy]')
) {
  polyfills.push(import('./scroll-target-group.js'));
}

if (
  !Object.prototype.hasOwnProperty.call(
    HTMLButtonElement.prototype,
    'interestForElement',
  ) &&
  document.querySelector('[interestfor]')
) {
  polyfills.push(import('./interestfor.js'));
}

await Promise.all(polyfills);

This browser

Status is read with a classic script before the deferred loader runs, so “Native” means the engine — not a polyfill that already patched the prototype.

Support in this user agent, captured before polyfills apply
Feature Detect This browser Native in
command / commandfor 'commandForElement' in HTMLButtonElement.prototype Needs JavaScript Chrome 135+, Firefox 144+, Safari 26.2+
closedby 'closedBy' in HTMLDialogElement.prototype Needs JavaScript Chrome 134+, Firefox 141+; Safari still preview
scroll-target-group CSS.supports('scroll-target-group: auto') Needs JavaScript Chrome/Edge 140+
interestfor HTMLButtonElement.prototype.hasOwnProperty('interestForElement') Needs JavaScript Chrome/Edge 142+

Invoker commands

File: src/polyfills/invoker-commands.js. MDN: Invoker Commands API . Used by Modal, Offcanvas, Dropdown, Popover, and the Scrollspy dropdown.

  • Loads when commandForElement is missing and the document has [commandfor].
  • Built-in commands: show-modal, close, request-close, show-picker, toggle-popover, show-popover, hide-popover (plus open-popover / close-popover aliases). Custom --* commands dispatch command and do nothing else.
  • The native command event does not bubble — listen on the target.
  • If a button also has popovertarget and the Popover API exists, the polyfill skips popover commands so the control is not toggled twice.

Try command / commandfor

HTML
<button type="button" class="btn btn-primary" command="show-modal" commandfor="polyfillInvokerModal">
  Open with command / commandfor
</button>

<dialog class="modal" id="polyfillInvokerModal" closedby="any" aria-labelledby="polyfillInvokerLabel">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title fs-5" id="polyfillInvokerLabel">Invoker commands</h1>
        <button type="button" class="btn-close" command="close" commandfor="polyfillInvokerModal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        This dialog opened through <code>command="show-modal"</code>. Close it with the controls or Escape.
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" command="close" commandfor="polyfillInvokerModal">Close</button>
      </div>
    </div>
  </div>
</dialog>

Dialog closedby

File: src/polyfills/dialog-closedby.js. MDN: HTMLDialogElement.closedBy . Used by Modal and Offcanvas static backdrops (closedby="none").

  • Loads when closedBy is missing and the document has dialog[closedby].
  • any: backdrop click requests close (cancel, then close).
  • none: Escape / close-request is cancelled.
  • closerequest and a missing attribute already match native modal Escape, so the polyfill does not extra-handle them.

Try closedby

Open each dialog and try Escape or a click on the backdrop.

HTML
<div class="d-flex flex-wrap gap-2">
  <button type="button" class="btn btn-primary" command="show-modal" commandfor="polyfillClosedbyAny">
    Light-dismiss (closedby="any")
  </button>
  <button type="button" class="btn btn-primary" command="show-modal" commandfor="polyfillClosedbyNone">
    Static (closedby="none")
  </button>
</div>

<dialog class="modal" id="polyfillClosedbyAny" closedby="any" aria-labelledby="polyfillClosedbyAnyLabel">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title fs-5" id="polyfillClosedbyAnyLabel">closedby="any"</h1>
        <button type="button" class="btn-close" command="close" commandfor="polyfillClosedbyAny" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        Backdrop click or Escape requests close.
      </div>
    </div>
  </div>
</dialog>

<dialog class="modal" id="polyfillClosedbyNone" closedby="none" aria-labelledby="polyfillClosedbyNoneLabel">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title fs-5" id="polyfillClosedbyNoneLabel">closedby="none"</h1>
        <button type="button" class="btn-close" command="close" commandfor="polyfillClosedbyNone" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        Backdrop click and Escape do nothing. Use Close.
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" command="close" commandfor="polyfillClosedbyNone">Close</button>
      </div>
    </div>
  </div>
</dialog>

scroll-target-group

File: src/polyfills/scroll-target-group.js. MDN: scroll-target-group . Used by Scrollspy.

  • Loads when CSS.supports('scroll-target-group: auto') is false and the document has [data-polyfill-scrollspy] — any element, not only Bootstrap navs. Put the attribute on the fragment-link group. An optional value is a scroller selector (data-polyfill-scrollspy="#pane"); an empty attribute uses the overflow ancestor of the targets.
  • An IntersectionObserver watches the overflow box that contains the targets and toggles a class named :target-current on the matching fragment link.
  • Styles use :is(:target-current, .\:target-current) so browsers that do not understand the pseudo-class still match the polyfill class.
  • The polyfill does not set aria-current. Native :target-current is visual only as well.

Try scroll-target-group

Scroll the pane. The list item that matches the heading in view is highlighted.

One

Placeholder copy so the scroll area is taller than the viewport of the example. Keep scrolling to move the current marker.

Two

Placeholder copy so the scroll area is taller than the viewport of the example. Keep scrolling to move the current marker.

Three

Placeholder copy so the scroll area is taller than the viewport of the example. Keep scrolling to move the current marker.

HTML
<div class="row">
  <div class="col-4">
    <div id="polyfillScrollspyNav" class="list-group" data-polyfill-scrollspy>
      <a class="list-group-item list-group-item-action" href="#polyfill-scroll-1">One</a>
      <a class="list-group-item list-group-item-action" href="#polyfill-scroll-2">Two</a>
      <a class="list-group-item list-group-item-action" href="#polyfill-scroll-3">Three</a>
    </div>
  </div>
  <div class="col-8">
    <div class="scrollspy-example" tabindex="0">
      <h4 id="polyfill-scroll-1">One</h4>
      <p>Placeholder copy so the scroll area is taller than the viewport of the example. Keep scrolling to move the current marker.</p>
      <h4 id="polyfill-scroll-2">Two</h4>
      <p>Placeholder copy so the scroll area is taller than the viewport of the example. Keep scrolling to move the current marker.</p>
      <h4 id="polyfill-scroll-3">Three</h4>
      <p>Placeholder copy so the scroll area is taller than the viewport of the example. Keep scrolling to move the current marker.</p>
    </div>
  </div>
</div>

Interest invokers

File: src/polyfills/interestfor.js. Adapted from Mason Freed’s interestfor (BSD-3-Clause). MDN: Using interest invokers . Used by Tooltip hint popovers.

  • Loads when interestForElement is missing on HTMLButtonElement.prototype and the document has [interestfor].
  • Hover, keyboard focus, or a long-press on a <button> shows the target. Long-press on links is not polyfilled (the browser context menu stays).
  • Opens a popover with showPopover({ source }) so CSS Anchor Positioning can tether. If the trigger has no anchor-name, the polyfill assigns one.
  • Delay is read from --interest-delay-start / --interest-delay-end. Hint tooltips also set the native interest-delay-* properties to 0s unless the author overrides --bs-tooltip-delay-*.
  • Does not polyfill popover="hint". Firefox 153+ has hint natively; Safari does not. Use the tooltip aria-description fallback there.
  • A plain hint gets aria-describedby. Native interestfor does that association itself — do not add those attributes on the native path.

Try interestfor

Hover or focus the button. In Chrome the engine opens the hint; elsewhere this page loads the polyfill.

Opened with interestfor
HTML
<span class="d-inline-block">
  <button type="button" class="btn btn-secondary" interestfor="polyfillTip">
    Hint tooltip
  </button>
  <div class="tooltip bs-tooltip-top" popover="hint" id="polyfillTip">
    <div class="tooltip-arrow"></div>
    <div class="tooltip-inner">Opened with interestfor</div>
  </div>
</span>