Bumps the development-dependencies group with 4 updates in the / directory: [@astrojs/markdown-remark](https://github.com/withastro/astro/tree/HEAD/packages/markdown/remark), [@stackblitz/sdk](https://github.com/stackblitz/sdk), [globby](https://github.com/sindresorhus/globby) and [js-yaml](https://github.com/nodeca/js-yaml).
Updates `@astrojs/markdown-remark` from 7.2.0 to 7.2.1
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/markdown/remark/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/@astrojs/markdown-remark@7.2.1/packages/markdown/remark)
Updates `@stackblitz/sdk` from 1.11.0 to 1.11.1
- [Changelog](https://github.com/stackblitz/sdk/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stackblitz/sdk/commits)
Updates `globby` from 16.2.0 to 16.2.1
- [Release notes](https://github.com/sindresorhus/globby/releases)
- [Commits](https://github.com/sindresorhus/globby/compare/v16.2.0...v16.2.1)
Updates `js-yaml` from 5.2.0 to 5.2.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.2.0...5.2.1)
Bump the github-actions group with 4 updates (#42666)
Bumps the github-actions group with 4 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action).
Updates `github/codeql-action/init` from 4.36.2 to 4.36.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a)
Updates `github/codeql-action/autobuild` from 4.36.2 to 4.36.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a)
Updates `github/codeql-action/analyze` from 4.36.2 to 4.36.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a)
Updates `github/codeql-action/upload-sarif` from 4.36.2 to 4.36.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a)
Mark Otto [Tue, 14 Jul 2026 15:30:09 +0000 (08:30 -0700)]
Docs: fix and expand customize/getting-started pages (#42609)
- css-variables: drop the redundant dark mode root-variables section, note
light-dark() resolution, add a component-token override example, remove the
stale v5.2.0 reference
- javascript: remove the duplicate React example callout
- migration: mention the v5-to-v6 migration skill for AI coding agents
- color-modes: document the actual v6 mechanism (light-dark() + color-scheme):
system preference by default with data-bs-theme override, and correct the
$color-mode-type default (media-query)
- components: write the Theme variants section (--bs-theme-* tokens, .theme-*
classes, usage examples)
- rtl: highlight the changed line in Required HTML; restore the border around
the live demo by wrapping it in bd-code-snippet
- optimize: add a lean-Sass commented-import example, clarify the lean-JS
side-effect import, and replace the Unused CSS stub with a working PurgeCSS
setup for static HTML
Mark Otto [Mon, 13 Jul 2026 04:01:17 +0000 (21:01 -0700)]
Datepicker: populate input with preselected dates on load (#42628)
Fixes #42616
Input and button datepickers are only created on focus/click, so
data-bs-selected-dates never showed up until you interacted with the
field. Only inline datepickers were auto-initialized on load.
Extend the DOMContentLoaded auto-init to also cover any datepicker with
preselected dates so the value renders immediately.
Mark Otto [Mon, 13 Jul 2026 04:00:46 +0000 (21:00 -0700)]
Drawer: fix double scrollbar on responsive/modal drawers (#42629)
Reset the native <dialog> UA `overflow: auto` (which made the dialog
itself a scroll container) and add `min-height: 0` to `.drawer-body`
so only the body scrolls. Previously the dialog and body each showed
a scrollbar when drawer content overflowed (e.g. the docs mobile nav).
Bumps the development-dependencies group with 3 updates in the / directory: [autoprefixer](https://github.com/postcss/autoprefixer), [postcss](https://github.com/postcss/postcss) and [prettier](https://github.com/prettier/prettier).
Updates `autoprefixer` from 10.5.1 to 10.5.2
- [Release notes](https://github.com/postcss/autoprefixer/releases)
- [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/autoprefixer/compare/10.5.1...10.5.2)
Updates `postcss` from 8.5.15 to 8.5.16
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.15...8.5.16)
Updates `prettier` from 3.8.4 to 3.9.4
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.4...3.9.4)
Mark Otto [Sat, 4 Jul 2026 03:49:42 +0000 (20:49 -0700)]
OTP: fix click-to-focus and overwrite-on-retype (#42524)
* OtpInput: fix click-to-focus and overwrite-on-retype
The single-input rewrite left two interaction gaps: clicking a slot
didn't position the caret there (slots had pointer-events: none and
focus always jumped to the end), and retyping inserted instead of
overwriting, so preceding digits shifted along.
Keep the single accessible input but make its interaction faithful to
the input-otp model:
- Represent the active slot as a selection range so the next keystroke
overwrites a filled slot or appends to an empty one
- Intercept single-char typing and backspace via beforeinput for
overwrite semantics; paste/autofill/IME still flow through input
- Make slots clickable (pointerdown) to position the caret, clamped to
the first empty slot
- Land focus on the first empty slot instead of the end; track the caret
with a document selectionchange listener
* bump bundlewatch
* OTP: focus the input natively on tap to fix iPadOS keyboard dismiss
The slots overlaid the input with pointer-events: none and the JS called
input.focus() programmatically after preventDefault()-ing the tap. On
iPadOS that raises the on-screen keyboard then dismisses it instantly,
because the keyboard must be raised by a genuine, un-prevented gesture on
the input itself.
Let the input receive taps (pointer-events: auto) so focus — and the
keyboard — come from the native gesture. Map the tap's x-coordinate to a
slot and set the caret in the focus handler once focus settles; when
already focused, reposition immediately (preventDefault is safe then).
Reported on iPadOS 26/27 by @coliff.
* Bump bundlewatch
* OtpInput: cover tap-to-focus, beforeinput, and caret behavior
Adds unit tests for the native tap-to-focus (iPadOS keyboard) fix,
beforeinput overwrite/backspace/append semantics, tap clamping, paste
caret placement, selectionchange sync, the public focus() method, and
beforeinput listener cleanup on dispose. Brings branch coverage back
above the 89% threshold.
Mark Otto [Wed, 1 Jul 2026 18:00:59 +0000 (11:00 -0700)]
More docs cleanup (#42610)
* v6: Preserve component radii after bumping $radius to 1rem
Changing the default $radius from .5rem to 1rem doubled every step in the
$radii scale. Remap component and root radius token references down the
scale so their absolute radii stay the same (--radius-5 -> --radius-3,
--radius-7 -> --radius-4, --radius-9 -> --radius-7), and align the
border-radius mixin defaults. The $radii map and .rounded-* utilities are
intentionally left on the new base so opt-in utilities become rounder.
* Revert "v6: Preserve component radii after bumping $radius to 1rem"
* Add --box-shadow-xs token and .shadow-xs utility
Introduce a smaller box shadow between the existing default and sm sizes,
and expose it through the shadow utilities.
* Docs: collapse page meta dependencies into a count with tooltip
Replace the inline list of dependency links with a single count that
reveals the full list on hover, and drop the now-unused .bd-page-meta
styles.
* Docs: shrink the table of contents as the footer scrolls into view
Add a small docs helper that tracks how far the footer overlaps the
viewport and exposes it as --bd-toc-footer-overlap, so the sticky TOC
height shrinks instead of sliding under the footer.
* Docs: refine example snippet and ad styling
Tokenize the example radius via --bd-example-radius, give resizable
examples an inset preview with a shadow-xs container, and simplify the
Carbon ad wrapper.
* Rebuild dist
Regenerate compiled CSS and JS to pick up the new --box-shadow-xs token
and resync the distributed bundles with source.
Replace the scroll-direction + sequential-offsetTop heuristic (which
depended on IntersectionObserver entry order and mis-highlighted when
several sections shared the viewport) with a deterministic geometric
model: the active section is the deepest one whose top has scrolled to
or above an activation line near the top of the scroll root, read fresh
from getBoundingClientRect on each (rAF-throttled) scroll. At the bottom
the last section wins; above the first nothing is active.
Also: settle on scrollend (with a scroll-idle fallback) to restore the
URL hash via replaceState and move focus to the target after a
smooth-scroll navigation; escape section ids via parseSelector so dotted
/special-character ids no longer throw; add a topMargin option for the
activation line.
* rewrite more, add debugger to docs to illustrate
* missing test file
* ScrollSpy: fix CI — cspell, bundlewatch, branch coverage
- Add 'Fpath' to the cspell dictionary (from the #%2Fpath encoded-id example)
- Bump the four JS bundlewatch thresholds for the rewritten ScrollSpy
- Add option-derived activation-line tests (topMargin %/px, derived rootMargin
with/without a scroll root, non-numeric topMargin, explicit rootMargin,
overflow state, observer rebuild no-op, resize listener add/remove) to lift
branch coverage from 88.63% back over the 89% threshold (now 89.21%)
* Skill: add ScrollSpy migration section to match the guide
Mirror the migration.mdx ScrollSpy changelog in the migration skill:
IntersectionObserver rewrite / activation line, removed offset+method
options, new topMargin, rootMargin default null, threshold default [0],
smoothScroll hash+focus restore, and getElementById id resolution.
Keeps the skill and guide consistent as this PR lands ScrollSpy.
dependabot[bot] [Tue, 30 Jun 2026 19:27:52 +0000 (21:27 +0200)]
Bump dependencies with 2 updates (#42606)
Bumps the development-dependencies group with 2 updates in the / directory: [autoprefixer](https://github.com/postcss/autoprefixer) and [rollup](https://github.com/rollup/rollup).
Updates `autoprefixer` from 10.5.0 to 10.5.1
- [Release notes](https://github.com/postcss/autoprefixer/releases)
- [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/autoprefixer/compare/10.5.0...10.5.1)
Updates `rollup` from 4.62.0 to 4.62.2
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.62.0...v4.62.2)
dependabot[bot] [Tue, 30 Jun 2026 19:20:58 +0000 (21:20 +0200)]
Bump js-yaml from 5.1.0 to 5.2.0 (#42603)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 5.1.0 to 5.2.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.1.0...5.2.0)
Mark Otto [Sun, 28 Jun 2026 20:59:52 +0000 (13:59 -0700)]
Add `CLAUDE.md` (#42160)
* Add a claude file
* Docs: route CLAUDE.md to a tool-agnostic AGENTS.md
Rename the agent guide to AGENTS.md (the emerging cross-tool convention
used by Copilot, Cursor, Codex, etc.) and leave CLAUDE.md as a one-line
`@AGENTS.md` pointer so Claude Code keeps working without duplicating
content. Heading made tool-agnostic.
Mark Otto [Sun, 28 Jun 2026 20:59:31 +0000 (13:59 -0700)]
Add a skill for v5 for v6 migration (#42349)
* wip
* Skill: correct v5→v6 migration guide and add new-component coverage
- Close button: keep the empty .btn-close (CSS-mask icon over currentcolor);
drop the wrong 'add a child SVG' instruction that double-rendered the icon,
and note .btn-close-white is gone (set text color instead)
- Border radius: remove the fabricated $border-radius-2xl rename; document the
real $radius base + $radii map (0-9) + --radius-* tokens
- Font sizes: note lg+ are fluid clamp() values (not exact rems) and list the
full xs-6xl scale
- Add a 'Rebuilt behavior & new components' section: carousel→scroll-snap with
opt-in autoplay, scrollspy→IntersectionObserver, the new components
(combobox, chips, datepicker, range, strength, otp-input, nav-overflow,
toggler), util/backdrop.js removal, CSS @layer, and --bs-*-rgb removal
- Extend the Phase 6 grep checklist for all of the above
* Skill: align migration guide with migration.mdx changelog
- Remove the 'ScrollSpy → IntersectionObserver' note: v5 (5.2+) already used
IntersectionObserver, so it's not a v5→v6 change (the docs changelog omits it)
- Expand carousel coverage to match the changelog: ride→autoplay, wrap→ends,
removed control/caption/dark/stacked classes, .carousel-overlay requirement,
control-icon renames
- Add the remaining new components (submenu, stepper, avatar, form-adorn, prose)
- Note the .rounded-* remap + --radius-pill, and link-*→underline-* utilities
- Extend the Phase 6 grep list accordingly
The Sass deprecation list said to use `$border-radius-2xl`, but the
Utilities section (and the source) removed the whole `$border-radius-*`
family in favor of the `$radii` map / `--radius-*` tokens. Point the
note there instead.
* Docs: note removal of focustrap and scrollbar utilities
#42595 removed util/focustrap.js and util/scrollbar.js (dead code after
the native <dialog> Dialog/Drawer rewrite). Record them alongside the
existing util/backdrop removal in both the migration skill and the
migration guide changelog.
* Fix CI on migration docs: lint, spelling
- Split the v5/v6 namespace-vs-import example into two code fences so the
duplicate `const tooltip` no longer trips eslint's no-redeclare, and add
the blank line after the import
- Add currentcolor, Datepicker, focustrap, oklab, oklch to the cspell
dictionary; reword 'inerting' and 'Unlayered' to avoid coined words
Mark Otto [Sun, 28 Jun 2026 20:59:04 +0000 (13:59 -0700)]
Prevent `.DS_Store` in our zip files (#42123)
* Remove .DS_Store from dist building
* fix
* Build: exclude .DS_Store via zip -x instead of deleting files
Use zip's own `-x "*.DS_Store"` exclude in both the examples zip and the
release-zip script, rather than finding and removing files first. Cleaner
(doesn't mutate dist), and fixes the eslint error from filtering inside a
for…of loop header.
Claudio B. [Sun, 28 Jun 2026 15:42:28 +0000 (17:42 +0200)]
v6: Remove extra bottom border on card navigation nav-tabs (#42571)
* v6: Remove extra bottom border on card navigation nav-tabs
Adding navigation to a card’s header with the current v6 adds an extra border which is visible in the documentation, see https://v6-dev--twbs-bootstrap.netlify.app/docs/6.0/components/card/#navigation
Mark Otto [Sun, 28 Jun 2026 15:37:02 +0000 (08:37 -0700)]
Remove unused FocusTrap and ScrollBarHelper utilities (#42595)
Both became dead code with the Modal->Dialog / Offcanvas->Drawer rewrite:
the native <dialog> element provides the focus trap and top-layer
inerting, and the scroll lock is now :root.dialog-open in CSS. Nothing in
js/src imports either utility and neither is part of the public bundle
export — only their own unit specs referenced them.
Removes the source modules, their committed dist builds, and their specs.
* Sanitizer: block data:/vbscript: URLs in allowed attributes
The sanitizer's SAFE_URL_PATTERN only rejected javascript:, so a
data:text/html (or vbscript:) URL in an href/src passed the allowList —
an XSS vector via data-bs-title/data-bs-content. Reject data: and
vbscript: in SAFE_URL_PATTERN and re-allow only safe base64 image/video/
audio data URLs via a restored DATA_URL_PATTERN. Fixes #42443.
* Bump bundlewatch size thresholds
* Sanitizer: drop redundant 0-9 from base64 data-URI char class
\d already matches 0-9, so the explicit 0-9 in the same class was dead
weight. Functionally identical; clears the CodeQL overly-permissive-range
alert on the overlap.
* Build: bump bundle.js bundlewatch threshold for sanitizer additions
Mark Otto [Sat, 27 Jun 2026 03:27:24 +0000 (20:27 -0700)]
Menu/Popover: setContent show gate + forms-in-menu auto-close (#42551)
* Popover/Tooltip: allow showing content set only via setContent()
show() gated on _isWithContent(), which read config title/content and
ignored content supplied through setContent(). A popover whose content
was set only via setContent() could therefore never be shown. Count
setContent() content in the gate via a shared _hasNewContent() helper.
Fixes #40525.
* Menu: keep open when clicking inside a form within the menu
clearMenus only skipped auto-close for input/select/option/textarea/form
tag names, so clicking a form's label/button/etc. inside a menu closed it
— breaking the documented forms-in-menu example. Skip auto-close for any
click inside a form contained in the menu. Fixes #41803.
* Tests: assert dialog-open on documentElement in dispose-while-open specs
The dispose-while-open specs added in #42544 asserted the scroll-lock
class on document.body, but #42545 moved the lock to the root element
(documentElement). The two changes were each green in isolation but
broke once both landed on v6-dev. Align these specs with the rest of
the suite, which already asserts on documentElement.
* Build: bump bundlewatch thresholds for cumulative JS growth on v6-dev
bootstrap.bundle.js (83.15KB) and bootstrap.js (54.39KB) now exceed the
prior thresholds after the dialog/drawer/tooltip/popover/menu fixes
landed; each PR bumped only relative to its own base.
Mark Otto [Sat, 27 Jun 2026 03:16:06 +0000 (20:16 -0700)]
Docs: navbar menu buttons, menu JS toggle, delegated tooltips (#42552)
* Docs: use <button> for navbar menu toggles instead of <a href="#">
A menu toggle is a button, not a link. Replace the navbar dropdown
toggles' `<a href="#" role="button">` with semantic `<button>`
(.nav-link already resets button styling). Fixes #40995.
* Docs: show how to open/close/toggle a menu via the JS API
The Via JavaScript section only showed instantiation. Add an example
using getOrCreateInstance().show()/hide()/toggle(). Fixes #37042.
* Docs: document delegated tooltips via the selector option
Add an example initializing one Tooltip on a parent with the selector
option to cover many (and dynamically-added) triggers. Fixes #41020.
Mark Otto [Sat, 27 Jun 2026 03:15:56 +0000 (20:15 -0700)]
Tooltip/Popover: fix prevented-show lockout and boolean title/content (#42548)
* Tooltip/Popover: reset hover state when show is prevented
A prevented (or not-in-DOM) show() returned early without clearing
_isHovered, leaving it stuck true. For click-triggered tips that made
every later click hit the _enter() early-return, so the tip never
reopened after one preventDefault(). Reset _isHovered on the early
return. Fixes #39861.
* Tooltip/Popover: coerce boolean title/content to strings
data-bs-title="true" / data-bs-content="false" are auto-converted to
booleans by the data-API, which failed the (null|string|element|function)
type check and threw. Extend the existing number-to-string coercion in
_configAfterMerge to booleans. Fixes #41925.
_keydown preventDefault'd arrow keys regardless of modifiers, so
Alt+Left/Right (browser history nav) and similar shortcuts were
swallowed inside a tablist. Ignore the event when alt/ctrl/meta is
held. Fixes #38565.
* Docs: clarify Escape behavior in menu auto-close section
Esc closes the menu while focus is within it (regardless of autoClose)
and returns focus to the toggle. Fixes #38035.
Mark Otto [Sat, 27 Jun 2026 03:15:31 +0000 (20:15 -0700)]
Tooltip/Popover/Menu: small fixes (contenteditable, arrow color, docs) (#42550)
* Menu: don't hijack arrow keys inside contenteditable elements
The keydown handler only exempted <input>/<textarea>, so arrow keys were
captured (and preventDefault'd) inside contenteditable hosts like rich-text
editors. Treat contenteditable targets like inputs. Fixes #41021.
* Popover: match bottom arrow fill to header background
A bottom-placed popover with a header had its arrow filled with
--popover-bg, so it didn't match the header it points into. Use
--popover-header-bg for the arrow when a header follows. Fixes #40993.
* Docs: clarify tooltip/popover title/content function arguments
The function receives the attached element as its first argument (and as
its this reference), not just this. Fixes #40571.
Mark Otto [Sat, 27 Jun 2026 03:15:07 +0000 (20:15 -0700)]
Dialog/Drawer: lock scroll on the root element to prevent layout shift (#42545)
* Dialog/Drawer: lock scroll on the root element to prevent layout shift
Apply the dialog-open scroll-lock (overflow: hidden) to the root <html>
element instead of <body>, so it sits on the same element as
scrollbar-gutter: stable. Co-locating them keeps the gutter reserved
while the scrollbar is hidden, so the page no longer shifts (and the
::backdrop covers the gutter instead of leaving a white strip) when a
dialog or drawer opens. Fixes #39221, #39972, #40908, #40659.
Mark Otto [Sat, 27 Jun 2026 03:14:40 +0000 (20:14 -0700)]
Dialog/Drawer: fix focus-restore scroll and dispose-while-open cleanup (#42544)
* Dialog/Drawer: don't scroll the page when restoring focus on close
Pass { preventScroll: true } when returning focus to the trigger after
close, so the page no longer jumps to the trigger (or to the top when
scroll-padding-top is set). Fixes #38070, #41615, #35391.
* Dialog/Drawer: restore body scroll when disposed while open
dispose() now closes the native <dialog> and removes the dialog-open
body class if the instance is torn down while still open (e.g. an SPA
route change), instead of leaving overflow: hidden stuck on the body.
Fixes #35934, #39910.
Christian Oliff [Fri, 26 Jun 2026 04:40:10 +0000 (13:40 +0900)]
Examples: remove unused `.text-small` and replace deprecated utilities (#42455)
Update multiple example pages to new utility naming and tokens for consistency. Replaced legacy text-*/display-*/bg-* classes with fg-*/fs-* and bg-subtle-* variants, adjusted spacing and radii (p-5 → p-9, rounded-3 → rounded-5), removed redundant text-small classes, and applied small/fg-white tweaks where appropriate. Files changed: checkout, headers, jumbotron, pricing, product, and sidebars examples to align markup with the updated design system.
Mark Otto [Tue, 23 Jun 2026 17:27:23 +0000 (10:27 -0700)]
Floating labels: place label before control for screen readers (#42539)
Floating labels required the control to precede the `<label>` in the DOM
so the floating animation could use a sibling (`~`) selector. That order
made screen readers (e.g. NVDA) announce the label after the field's
value instead of before it.
Switch the SCSS to look forward with `:has()` so the `<label>` can come
first in the DOM while the CSS still reacts to the control's state
(focus, value, disabled, autofill). Reorder all examples, docs, and the
visual test to label-first markup to match.
Also give `.form-control-plaintext` its own copy of the control tokens so
its `var(--control-*)` references resolve, fixing phantom borders and
label misalignment.
Mark Otto [Mon, 22 Jun 2026 17:51:31 +0000 (10:51 -0700)]
Add Range plugin for track fill, value bubble, and tick marks (#42525)
* Add Range plugin for track fill, value bubble, and tick marks
Adds an opt-in JavaScript plugin (`data-bs-range`) that enhances
`<input type="range">`. A consistent cross-browser fill can't be done
with pseudo-elements alone (only Firefox has `::-moz-range-progress`),
so the plugin publishes the current value as a `--bs-range-value`
custom property that the track gradient consumes.
- Fill: colored track up to the thumb, themeable via `--range-fill-bg`
- Value bubble (`data-bs-bubble`): floating value that tracks the thumb
- Tick marks (`data-bs-ticks`): generated from a linked `<datalist>`
The bubble and ticks are siblings of the input, so they don't inherit
the input's `--range-fill-bg`; the plugin copies the resolved value onto
them and the CSS falls back to `--primary-base` so they're never blank.
Plain `.form-range` inputs are untouched. Includes unit tests and docs.
* Range: anchor positioning on .form-range and rename track fill token
- Make `.form-range` the positioning context (`position: relative`) and
drop the JS-added `.range-anchored` class; the bubble and ticks are
siblings of the input, so they already share its offset parent.
- Rename `--range-fill-bg` to `--range-track-fill-bg` for accuracy.
- Keep SCSS custom properties unprefixed (the build adds `--bs-`) and
have the plugin read/write the prefixed names, matching the codebase.
- Trim obvious comments.
* Bump bundlewatch JS size limits for the Range plugin
* Range: make it a JS component with a wrapper, consolidate the CSS
Restructure range as an always-JS component so the fill, value bubble,
and tick marks are driven by a single `--bs-range-fill` ratio in CSS.
- `.form-range` is now a wrapper that owns the tokens; the input takes
`.form-range-input`. Children inherit the tokens, so the old JS color
copying and px positioning are gone.
- The plugin only sets `--bs-range-fill` (0–1) and the bubble text; the
track gradient, bubble position, and tick positions are pure CSS calc.
Drops `_thumbWidth`, `_inheritFillColor`, the resize listener, and the
`.range-anchored` class.
- Value bubble reuses the tooltip markup/styles instead of duplicating a
pill and arrow. Tick marks are generated from the linked `<datalist>`
and positioned per-value (handles uneven values); tick coloring dropped.
- Auto-inits every `.form-range`; rename fill token to
`--range-track-fill-bg`.
- Validation moves to `.form-range-input` with a `:has()` feedback toggle.
- Add the "New" badge to the Range sidebar item and document the breaking
changes in the migration guide.
* Range: lay out tick marks with CSS grid instead of absolute positioning
Build `grid-template-columns` from the gaps between the datalist values
so each tick lands on a grid line — this handles unevenly-spaced values
just like the old per-tick calc did, but keeps the ticks and their labels
in normal flow (the container sizes to fit the labels instead of them
overflowing an absolutely-positioned box). Inset by half the thumb so the
end ticks align with the thumb travel.
Also wraps the token map in the `custom-property-no-missing-var-function`
stylelint disable (matching _strength.scss). Includes the disabled-track
fill styling.
* Nudge bundlewatch JS limits after rebasing onto v6-dev
Mark Otto [Sun, 21 Jun 2026 04:52:17 +0000 (21:52 -0700)]
feat(a11y): use list-style-type "" instead of list-style none (#42526)
In Safari, "list-style: none" removes the list semantic role, so lists are
no longer announced by screen readers. Using "list-style-type: \"\"" keeps the
same visual result while preserving the list role.
Ports twbs/bootstrap#41778 to v6, plus the new v6 list containers (stepper,
menu, docs search results). Summary marker removals (accordion header,
details summary) are intentionally left as "list-style: none".
Mark Otto [Thu, 18 Jun 2026 02:31:20 +0000 (19:31 -0700)]
Convert list group and card group to container queries (#42512)
* Convert list group and card group to container queries
Replace viewport media queries with container queries for the responsive
.*:list-group-horizontal variants and the .card-group row layout, using the
existing container-breakpoint-up mixins. Both now respond to the width of a
parent query container (e.g., .contains-inline) rather than the viewport.
Update the docs to reflect the new behavior, flip css_media to container, and
list both components under the container queries guide.
* Document container query migration for card group and list group
* Fix doubled card-group borders by overlapping adjacent cards
The v6 border model moved borders off the outer .card onto the inner
segments (.card-header, .card-body, .card-list, .card-footer) and the card
images use outlines. The card-group seam code still removed border-inline-start
from .card, which no longer has a border, so adjacent cards rendered doubled
borders at every seam (body, header, and footer).
Pull each subsequent card back by one border width with a negative inline
margin so its leading edges overlap the previous card's trailing edge,
collapsing every seam into a single line.
* Fix doubled border between card body and footer
A middle .card-body/.card-list draws a bottom border to divide itself from the
next segment, but .card-footer draws a full border including its top edge, so
the shared seam rendered as a doubled border whenever a footer followed a body
that wasn't the first child (e.g. image + body + footer, or header + body +
footer). Drop the body/list bottom border when a footer immediately follows so
the footer owns that divider.
Mark Otto [Thu, 18 Jun 2026 02:25:44 +0000 (19:25 -0700)]
Dispose existing instance when a component is re-instantiated on an element (#42509)
Re-creating a component on an element that already has one (e.g. `new
Toast(el)` twice) silently overwrote the instances registry, orphaning the
previous instance with its event listeners and timers still attached. Dispose
the existing instance before registering the new one so it is cleaned up
instead of leaking, while keeping `getInstance()` returning the latest instance.
Also guard `_queueCallback` so a transition completion callback is skipped once
the instance has been disposed, preventing the `this._element is null` error
when `dispose()` is called mid-transition.
Mark Otto [Thu, 18 Jun 2026 02:20:58 +0000 (19:20 -0700)]
Carousel: fix Safari zoom scroll overshoot with a JS-driven slide animation (#42506)
Under Safari page zoom, programmatic smooth scrolls (scrollBy/scrollTo with
behavior smooth) mis-scale and overshoot the target: a one-slide jump sails
well past it and the restored scroll-snap visibly yanks the slide back. This
happens whether or not snapping is suspended during the scroll, so no
snap-configuration tweak avoids it - native smooth scrolling driven by user
gestures is fine, but any programmatic smooth scroll is affected.
Replace the native smooth scroll + settle-watcher with a JS-driven rAF
animation (_animateScroll): step scrollLeft to the target over SCROLL_DURATION
(300ms) with an ease-in-out cubic, using instant scrolls per frame. This
sidesteps Safari's bug entirely and gives every programmatic jump (prev/next,
indicators, wrap, and the seamless loop) a consistent duration. Reduced motion
and the no-requestAnimationFrame path jump straight to target.
Removes the now-unneeded _restoreSnapWhenSettled/_afterScrollSettles
overshoot-detection machinery. Reworks the unit tests to the new seam and adds
coverage for the animator: eased stepping, frame cancellation, reduced motion,
and the no-rAF fallback.
dependabot[bot] [Tue, 16 Jun 2026 13:15:32 +0000 (15:15 +0200)]
Build(deps-dev): Bump dependencies with 3 updates (#42517)
Bumps the development-dependencies group with 3 updates in the / directory: [jasmine](https://github.com/jasmine/jasmine-npm), [prettier](https://github.com/prettier/prettier) and [rollup](https://github.com/rollup/rollup).
Updates `jasmine` from 6.2.0 to 6.3.0
- [Release notes](https://github.com/jasmine/jasmine-npm/releases)
- [Changelog](https://github.com/jasmine/jasmine-npm/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine-npm/compare/v6.2.0...v6.3.0)
Updates `prettier` from 3.8.3 to 3.8.4
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.3...3.8.4)
Updates `rollup` from 4.61.0 to 4.61.1
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.61.0...v4.61.1)
Mark Otto [Thu, 11 Jun 2026 16:56:38 +0000 (09:56 -0700)]
Rework OTP input as a single accessible input with rendered slots (#42500)
* Rework OTP input as a single accessible input with rendered slots
Rebuild the OTP component around one real <input> rendered as separate
digit slots (the input-otp/shadcn model) so assistive tech, password
managers, and SMS autofill treat it as a single field — addressing the
accessibility feedback in twbs/discussions#42486.
- Replace the six-input markup and per-digit aria-labels with one labelled input
- Add a type option (numeric/alphanumeric/alpha) and glyph-based masking
- Make length a real option (falls back to maxlength); add groups/separator
- Standardize naming on bs.otpInput; events complete.bs.otpInput /
input.bs.otpInput now expose event.value
- Move validation styling onto the rendered .otp-slot elements
- Rewrite docs and unit tests for the single-input model
* Fix bundlewatch sizes and cspell dictionary for OTP input
- Bump bundlewatch maxSize thresholds to account for the OTP component
- Add "autofilled" to the cspell dictionary used in the OTP docs
Mark Otto [Thu, 11 Jun 2026 16:48:28 +0000 (09:48 -0700)]
Fix dark mode on built site by bumping browserslist (#42471)
* Fix dark mode on built site by avoiding Lightning CSS light-dark() polyfill
Lightning CSS was lowering native light-dark() to a --lightningcss-light/dark
custom-property toggle because our browserslist floors predated native support.
That polyfill breaks data-bs-theme dark mode on the minified (production) CSS,
so dark examples rendered light on Netlify while working in dev.
- Bump browserslist floors to the first versions with native light-dark()
(Chrome/Edge 123, Safari/iOS 17.5; Firefox 121 already covered)
- Exclude Features.LightDark in css-minify as a guard against regressions
* Update .browserslistrc for browser support and comments
Removed comment about CSS light-dark polyfill and updated browser support.
Raises support floors as far as possible while keeping ~90.8% global
coverage, staying on versions with native light-dark() so Lightning CSS
does no polyfilling.
Mark Otto [Thu, 11 Jun 2026 16:46:59 +0000 (09:46 -0700)]
Carousel: scroll-snap rebuild, opt-in autoplay, play/pause, and UI redesign (#42484)
* Make carousel autoplay opt-in via boolean `autoplay` option
Replace the `ride` option with a boolean `autoplay` option (default
`false`), so carousels are static by default and only autoplay when
explicitly opted in with `data-bs-autoplay="true"`.
This removes the confusing `ride="true"` behavior that started
autoplaying only after the first user interaction, and the cryptic
`ride="carousel"` string value. Addresses the first two points of #32649.
* Stop carousel autoplay on interaction and add play/pause control
Address the WCAG 2.2.2 (Pause, Stop, Hide) concern in #32649.
Behavior: once a user takes control of an autoplaying carousel — clicking
a control or indicator, using the keyboard, or swiping — autoplay stops
for good instead of resuming, respecting their intent. A new runtime
`_playing` flag tracks autoplay intent and gates `_maybeEnableCycle()`.
Control: add a `.carousel-control-play-pause` button as the discoverable
pause/stop mechanism WCAG actually requires (a hover-only pause does not
qualify). It toggles autoplay, reflects state by swapping its icon (via
pause-fill/play-fill CSS mask icons) and `aria-label`, and can also start
autoplay on an otherwise static carousel.
Includes unit tests, SCSS tokens/styles with dark-mode support, docs, and
a migration note.
* Rebuild carousel on CSS scroll snap
Replace the float/translateX engine and custom swipe handler with a native
horizontal scroll-snap container. Sliding, touch dragging, momentum, and
keyboard scrolling now come from the browser; JavaScript only layers on
autoplay, the prev/next/indicator controls, and active-slide syncing via an
IntersectionObserver.
This unlocks features the old engine couldn't do, all via CSS custom
properties on `.carousel`:
- Multiple slides per view (`--carousel-items`) with responsive
`.carousel-items-*` utilities
- Peek of adjacent slides (`--carousel-peek`) and gaps (`--carousel-gap`)
- Variable-width slides (`.carousel-auto`) and center mode (`.carousel-center`)
`.carousel-fade` becomes a stacked-opacity mode that upgrades to a View
Transition where supported. `touch: false` now applies `touch-action: pan-y`.
The markup, public JS API, and slide/slid events are preserved; the
transitional `.carousel-item-{start,end,next,prev}` classes and the
`.carousel.pointer-event` helper are removed.
Includes a full unit-test rewrite, SCSS engine, docs with multi-item/peek/
variable-width examples, and a migration entry.
* Add carousel stacked layout and dot indicator variants
Rename the per-slide layout tokens for clarity and add new layout options:
- `--carousel-peek` -> `--carousel-items-peek`, `--carousel-gap` ->
`--carousel-items-gap` (both now carry a length unit so they're safe inside
the flex-basis `calc()`)
- New `--carousel-gap` for spacing the stacked layout's rows
- New `.carousel-stacked` layout (controls outside the inner viewport, above or
below) and `.carousel-indicators-dots` round indicator style
- Fix the `.carousel-item` flex-basis so the peek isn't subtracted twice (the
`padding-inline` already insets the content box)
* Add carousel `ends` option (stop/wrap/loop) and robust scroll navigation
Replace the `wrap` boolean with an `ends` option taking `stop`, `wrap`
(default), or `loop`:
- `stop` disables the prev/next controls at each end (moving focus to the
opposite control first, so focus is never lost)
- `wrap` jumps from the last slide back to the first (and vice versa)
- `loop` continues seamlessly into a transient clone of the destination slide,
then teleports to the real one with no visible backward jump (single-slide
scroll layouts only; otherwise falls back to `wrap`)
Rework programmatic navigation to scroll the viewport directly with `scrollBy`
instead of `scrollIntoView`, suspending scroll-snap for the duration and
restoring it once the scroll settles. This fixes multi-slide jumps (indicator
clicks, reaching the last slide) that `scroll-snap-stop: always` previously
clamped to one slide, avoids yanking the page to an off-screen carousel, and
works in RTL. Navigation steps are now measured from the live scroll position
so a stale active index can't make a control silently no-op.
Includes unit tests covering the ends modes, the loop transition, end-control
disabling, and the scroll-settle behavior.
* Document carousel end behavior, stacked layout, and indicator variants
Add docs and examples for the new stacked carousels (top, bottom, and with
dot indicators), the `ends` option (wrap/stop/loop) under a new "End behavior"
section, and refresh the options table and intro for the scroll-snap model.
* Redesign carousel controls, indicators, and layout
- `.carousel` is now a flex column by default (controls/indicators sit in the
flow above or below the slides); overlaying them on top of the slides moves
to a new `.carousel-overlay` modifier.
- Rename the control-icon classes to `.carousel-icon-{prev,next,pause,play}`,
painted with `currentcolor` so they inherit the surrounding text/button color
and work inside `.btn-*`. `.carousel-control-play-pause` is now just a
behavior hook that shows the matching glyph via `.paused`.
- Redesign indicators as pills: the active one widens, and while autoplaying it
fills like a progress bar over the slide's interval
(`carousel-indicator-progress` keyframes driven by `--carousel-interval`).
- Remove `.carousel-caption` and its `--carousel-caption-*` tokens.
* Add carousel autoplay progress, drop `touch` option and View Transitions
- While cycling, toggle a `.carousel-playing` class and expose the wait as
`--carousel-interval` so CSS can animate the active indicator's progress
fill. Schedule each tick from the slide being navigated *to*, so per-item
`data-bs-interval`s don't lag a slide behind.
- Remove the `touch` option: horizontal dragging is part of the native
scroll-snap container, so there's nothing left for JavaScript to toggle.
- Fade is now a plain CSS opacity crossfade; drop the View Transition path,
which double-animated against the CSS transition and visibly stuttered.
* Update carousel docs, examples, and fixtures for the redesign
Switch the docs examples, homepage/cheatsheet examples, and test fixtures to
`.carousel-overlay` + `.carousel-icon-*` and drop `.carousel-caption`. Document
the stacked-by-default layout, the `.carousel-overlay` modifier, the renamed
control-icon classes, the autoplay progress indicator, and the removed `touch`
option and captions in the migration guide.
* fix bundlewatch
* Fix MDX emphasis-style lint error in migration guide
* Default carousel `ends` to `loop`
Change the default end behavior from `wrap` to `loop`, so carousels scroll
seamlessly past the ends by default (falling back to `wrap` where seamless
looping doesn't apply). Tests that exercise the wrap jump now request it
explicitly.
* Clean up carousel styles and refresh docs
- Remove the legacy `--carousel-control-*` tokens and the entire
`carousel-dark-*` token map / `.carousel-dark` block and dark color-mode
rule; dark styling now rides on `light-dark()` in `.carousel-overlay`.
- Flip RTL prev/next icons with `transform: scaleX(-1)` instead of swapping
mask images, and drop the dead commented-out overlay rules.
- Drop the now-unused `colors` and `color-mode` Sass imports.
- Docs: lead the end-behavior section with `loop` (the new default), rework
the dark example onto `.carousel-overlay-controls` + `.btn-*` controls, and
round the slide corners.
* Remove unused `$enable-dark-mode` Sass flag
Its last consumer was the carousel's dark color-mode block, now removed in
favor of `light-dark()`, so the flag is no longer referenced anywhere.
* Clean up carousel autoplay timer and listeners on dispose
Clear the pending autoplay timer in `dispose()` so a queued tick can't fire
after teardown and throw on the now-null `_element`, and remove the
`pointerdown` listener bound to the viewport (`.carousel-inner`)—which
`super.dispose()` doesn't cover, since it only drops listeners on `_element`.
Adds tests for both.
* Simplify carousel active indicator tokens
Drop the `--carousel-indicator-active-width` and `--carousel-indicator-active-bg`
tokens, which only carried `null` fallbacks, and inline their values directly on
the active indicator.
* Use button controls in carousel examples, fixtures, and docs
Replace the removed `.carousel-control-prev`/`.carousel-control-next` classes
with `.btn-icon btn-sm` buttons across the docs examples, homepage and cheatsheet
examples, and JS test fixtures. Also drop the leftover `.carousel-indicators-dots`
class and the obsolete "Custom transition" docs section, and correct the
peek/gap token names (`--carousel-items-peek`/`-gap`) in the migration guide.
* Remove `$enable-dark-mode` from the customization docs
Follow-up to removing the now-unused flag: drop its row from the global options
table and the sentence about disabling dark mode via Sass.
* Bump JS bundlewatch size limits for the carousel rewrite
* Harden carousel navigation, looping, and autoplay edge cases
Audit fixes:
- Measure the "current" slide in `to()` from the live scroll position
(`_navIndex()`) rather than the async `_activeIndex`, so an indicator/control
used mid-scroll compares against where the viewport actually rests.
- Validate the `ends` option in `_configAfterMerge`, falling back to the
default (`loop`) for unknown values so navigation and end-control logic agree.
- Strip ids from the entire cloned subtree during a loop transition, not just
the clone root, to avoid duplicate ids while the clone is on screen.
- Sync the active slide and fire `slid` after a programmatic scroll settles
when no IntersectionObserver is available.
- Stop autoplay at the last slide under `ends: 'stop'` instead of re-arming a
timer that can never advance.
Adds tests for each, plus hover-pause and dispose-mid-loop coverage.
The redesigned pill indicators no longer fade via opacity, so the token has no
remaining consumers.
* Expand carousel docs, examples, and migration notes
- Document the `ends` option, the removed `.carousel-control-*` / `.carousel-dark`
classes and v5 tokens, and the removed `$enable-dark-mode` flag in the
migration guide.
- Clarify the `.active` starting-slide and indicator notes, theme the overlay
example controls (`.btn-subtle .theme-secondary`), rework the per-item
interval example with a play/pause control, and sharpen the `slid` event
description.
- Add a play/pause control to the integration fixture, and fix a corrupted
custom-property reference in the homepage carousel example CSS.
* Bump JS bundlewatch limits for carousel audit fixes