]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
v6: Refresh navbar, offcanvas, Components nav, and clean up themes docs (#42048)
authorMark Otto <markd.otto@gmail.com>
Sat, 7 Mar 2026 23:59:35 +0000 (15:59 -0800)
committerGitHub <noreply@github.com>
Sat, 7 Mar 2026 23:59:35 +0000 (15:59 -0800)
* Add some CSS vars to nav component, use nav in sidebar more, update toc and ads

* Massive update

* fix cspell

* More improvements

* update icons

* more updates

* revamp offcanvas

* Fix up few things

* order

* bump bundle

* unsure why this doesn't match locally

* Nav, stacks, and more improved

* color mode fixes

* sooooo much better

* cleanup, prevent lightningcss color-mix() to lab() conversion, remove more

* bump

* fixes

* more improvements

* more

70 files changed:
.bundlewatch.config.json
.cspell.json
js/index.esm.js
js/index.umd.js
js/src/dropdown.js
js/src/nav-overflow.js [new file with mode: 0644]
js/tests/unit/dropdown.spec.js
js/tests/unit/nav-overflow.spec.js [new file with mode: 0644]
scss/_colors.scss
scss/_config.scss
scss/_dialog.scss
scss/_functions.scss
scss/_nav-overflow.scss [new file with mode: 0644]
scss/_nav.scss
scss/_navbar.scss
scss/_offcanvas.scss
scss/_root.scss
scss/_variables.scss
scss/bootstrap.scss
scss/content/_reboot.scss
scss/forms/_chip-input.scss
scss/forms/_form-control.scss
scss/forms/_labels.scss
scss/helpers/_stacks.scss
scss/layout/_breakpoints.scss
scss/mixins/_backdrop.scss
site/data/sidebar.yml
site/src/assets/examples/album-rtl/index.astro
site/src/assets/examples/album/index.astro
site/src/assets/examples/carousel-rtl/index.astro
site/src/assets/examples/carousel/index.astro
site/src/assets/examples/cheatsheet-rtl/index.astro
site/src/assets/examples/cheatsheet/index.astro
site/src/assets/examples/navbar-bottom/index.astro
site/src/assets/examples/navbar-fixed/index.astro
site/src/assets/examples/navbar-static/index.astro
site/src/assets/examples/navbars-offcanvas/index.astro
site/src/assets/examples/navbars/index.astro
site/src/assets/examples/offcanvas-navbar/index.astro
site/src/assets/examples/product/index.astro
site/src/assets/examples/sticky-footer-navbar/index.astro
site/src/components/header/Navigation.astro
site/src/components/icons/HamburgerIcon.astro
site/src/components/shortcodes/CloseButton.astro
site/src/components/shortcodes/Code.astro
site/src/components/shortcodes/NavbarPlacementPlayground.astro [new file with mode: 0644]
site/src/components/shortcodes/ResizableExample.astro [new file with mode: 0644]
site/src/content/docs/components/alert.mdx
site/src/content/docs/components/dialog.mdx
site/src/content/docs/components/dropdown.mdx
site/src/content/docs/components/nav-overflow.mdx [new file with mode: 0644]
site/src/content/docs/components/navbar.mdx
site/src/content/docs/components/navs-tabs.mdx
site/src/content/docs/components/offcanvas.mdx
site/src/content/docs/components/scrollspy.mdx
site/src/content/docs/components/toasts.mdx
site/src/content/docs/customize/color-modes.mdx
site/src/content/docs/customize/color.mdx
site/src/content/docs/customize/sass.mdx
site/src/content/docs/customize/theme.mdx
site/src/content/docs/helpers/stacks.mdx
site/src/content/docs/helpers/stretched-link.mdx
site/src/content/docs/layout/breakpoints.mdx
site/src/content/docs/utilities/overflow.mdx
site/src/content/docs/utilities/shadows.mdx
site/src/layouts/DocsLayout.astro
site/src/scss/_component-examples.scss
site/src/scss/_content.scss
site/src/scss/_navbar.scss
site/src/types/auto-import.d.ts

index c781a9bb45f3ef9ba1a0a61acae688ca4137391f..0b79e5e6fb56e868cede9d19ab2ee66f2b773fa6 100644 (file)
@@ -2,11 +2,11 @@
   "files": [
     {
       "path": "./dist/css/bootstrap-grid.css",
-      "maxSize": "9.25 kB"
+      "maxSize": "8.75 kB"
     },
     {
       "path": "./dist/css/bootstrap-grid.min.css",
-      "maxSize": "10.0 kB"
+      "maxSize": "8.0 kB"
     },
     {
       "path": "./dist/css/bootstrap-reboot.css",
     },
     {
       "path": "./dist/css/bootstrap-reboot.min.css",
-      "maxSize": "6.5 kB"
+      "maxSize": "5.0 kB"
     },
     {
       "path": "./dist/css/bootstrap-utilities.css",
-      "maxSize": "14.5 kB"
+      "maxSize": "15.75 kB"
     },
     {
       "path": "./dist/css/bootstrap-utilities.min.css",
-      "maxSize": "15.25 kB"
+      "maxSize": "15.0 kB"
     },
     {
       "path": "./dist/css/bootstrap.css",
-      "maxSize": "41.0 kB"
+      "maxSize": "42.5 kB"
     },
     {
       "path": "./dist/css/bootstrap.min.css",
-      "maxSize": "39.5 kB"
+      "maxSize": "39.25 kB"
     },
     {
       "path": "./dist/js/bootstrap.bundle.js",
-      "maxSize": "68.5 kB"
+      "maxSize": "70.5 kB"
     },
     {
       "path": "./dist/js/bootstrap.bundle.min.js",
-      "maxSize": "41.75 kB"
+      "maxSize": "43.0 kB"
     },
     {
       "path": "./dist/js/bootstrap.esm.js",
-      "maxSize": "38.75 kB"
+      "maxSize": "40.75 kB"
     },
     {
       "path": "./dist/js/bootstrap.esm.min.js",
-      "maxSize": "24.0 kB"
+      "maxSize": "25.5 kB"
     },
     {
       "path": "./dist/js/bootstrap.js",
-      "maxSize": "39.5 kB"
+      "maxSize": "41.5 kB"
     },
     {
       "path": "./dist/js/bootstrap.min.js",
-      "maxSize": "21.25 kB"
+      "maxSize": "22.75 kB"
     }
   ],
   "ci": {
index 8fce1c3281e2c0648f541242caef760b52e1ee47..0b993929e98a10d46b3c36d1e38a5d5d1dc23896 100644 (file)
@@ -73,6 +73,7 @@
     "mouseleave",
     "navbars",
     "navs",
+    "navoverflow",
     "Neue",
     "noindex",
     "Noto",
index 01d298e05fce7d851e5a0fc0438daaea0c05a34c..e52911e2accd3bec4c07f78f9f0583014cb14b54 100644 (file)
@@ -12,6 +12,7 @@ export { default as Collapse } from './src/collapse.js'
 export { default as Datepicker } from './src/datepicker.js'
 export { default as Dialog } from './src/dialog.js'
 export { default as Dropdown } from './src/dropdown.js'
+export { default as NavOverflow } from './src/nav-overflow.js'
 export { default as Offcanvas } from './src/offcanvas.js'
 export { default as Strength } from './src/strength.js'
 export { default as OtpInput } from './src/otp-input.js'
index 73f12b424edd2c2429f3279bc3277b7e6b1cda76..73e7b45c83d6960f44ce8de91eb35282a35b3c46 100644 (file)
@@ -12,6 +12,7 @@ import Collapse from './src/collapse.js'
 import Datepicker from './src/datepicker.js'
 import Dialog from './src/dialog.js'
 import Dropdown from './src/dropdown.js'
+import NavOverflow from './src/nav-overflow.js'
 import Offcanvas from './src/offcanvas.js'
 import Strength from './src/strength.js'
 import OtpInput from './src/otp-input.js'
@@ -30,6 +31,7 @@ export default {
   Datepicker,
   Dialog,
   Dropdown,
+  NavOverflow,
   Offcanvas,
   Strength,
   OtpInput,
index 86955dd597385d91109da146f3874e641d31bd88..2053b46dc7a5670d17cc2f32dd52380eae89c1bc 100644 (file)
@@ -97,11 +97,13 @@ const triangleSign = (p1, p2, p3) =>
 const Default = {
   autoClose: true,
   boundary: 'clippingParents',
+  container: false,
   display: 'dynamic',
   offset: [0, 2],
   floatingConfig: null,
   placement: DEFAULT_PLACEMENT,
   reference: 'toggle',
+  strategy: 'absolute',
   // Submenu options
   submenuTrigger: 'both', // 'click', 'hover', or 'both'
   submenuDelay: SUBMENU_CLOSE_DELAY
@@ -110,11 +112,13 @@ const Default = {
 const DefaultType = {
   autoClose: '(boolean|string)',
   boundary: '(string|element)',
+  container: '(string|element|boolean)',
   display: 'string',
   offset: '(array|string|function)',
   floatingConfig: '(null|object|function)',
   placement: 'string',
   reference: '(string|element|object)',
+  strategy: 'string',
   submenuTrigger: 'string',
   submenuDelay: 'number'
 }
@@ -145,6 +149,9 @@ class Dropdown extends BaseComponent {
       SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||
       SelectorEngine.findOne(SELECTOR_MENU, this._parent)
 
+    // Store original menu parent for container option
+    this._menuOriginalParent = this._menu?.parentNode
+
     // Parse responsive placements on init
     this._parseResponsivePlacements()
 
@@ -185,6 +192,9 @@ class Dropdown extends BaseComponent {
       return
     }
 
+    // Move menu to container if specified (to escape overflow clipping)
+    this._moveMenuToContainer()
+
     this._createFloating()
 
     // If this is a touch-enabled device we add extra
@@ -220,6 +230,7 @@ class Dropdown extends BaseComponent {
 
   dispose() {
     this._disposeFloating()
+    this._restoreMenuToOriginalParent()
     this._disposeMediaQueryListeners()
     this._closeAllSubmenus()
     this._clearAllSubmenuTimeouts()
@@ -252,6 +263,9 @@ class Dropdown extends BaseComponent {
 
     this._disposeFloating()
 
+    // Restore menu to original parent if it was moved
+    this._restoreMenuToOriginalParent()
+
     this._menu.classList.remove(CLASS_NAME_SHOW)
     this._element.classList.remove(CLASS_NAME_SHOW)
     this._parent.classList.remove(CLASS_NAME_SHOW)
@@ -326,7 +340,8 @@ class Dropdown extends BaseComponent {
       referenceElement,
       this._menu,
       floatingConfig.placement,
-      floatingConfig.middleware
+      floatingConfig.middleware,
+      floatingConfig.strategy
     )
   }
 
@@ -434,7 +449,8 @@ class Dropdown extends BaseComponent {
   _getFloatingConfig(placement, middleware) {
     const defaultConfig = {
       placement,
-      middleware
+      middleware,
+      strategy: this._config.strategy
     }
 
     return {
@@ -450,8 +466,40 @@ class Dropdown extends BaseComponent {
     }
   }
 
+  _getContainer() {
+    const { container } = this._config
+    if (container === false) {
+      return null
+    }
+
+    return container === true ? document.body : getElement(container)
+  }
+
+  _moveMenuToContainer() {
+    const container = this._getContainer()
+    if (!container || !this._menu) {
+      return
+    }
+
+    // Only move if not already in the container
+    if (this._menu.parentNode !== container) {
+      container.append(this._menu)
+    }
+  }
+
+  _restoreMenuToOriginalParent() {
+    if (!this._menuOriginalParent || !this._menu) {
+      return
+    }
+
+    // Only restore if menu was moved
+    if (this._menu.parentNode !== this._menuOriginalParent) {
+      this._menuOriginalParent.append(this._menu)
+    }
+  }
+
   // Shared helper for positioning any floating element
-  async _applyFloatingPosition(reference, floating, placement, middleware) {
+  async _applyFloatingPosition(reference, floating, placement, middleware, strategy = 'absolute') {
     if (!floating.isConnected) {
       return null
     }
@@ -459,7 +507,7 @@ class Dropdown extends BaseComponent {
     const { x, y, placement: finalPlacement } = await computePosition(
       reference,
       floating,
-      { placement, middleware }
+      { placement, middleware, strategy }
     )
 
     if (!floating.isConnected) {
@@ -467,7 +515,7 @@ class Dropdown extends BaseComponent {
     }
 
     Object.assign(floating.style, {
-      position: 'absolute',
+      position: strategy,
       left: `${x}px`,
       top: `${y}px`,
       margin: '0'
diff --git a/js/src/nav-overflow.js b/js/src/nav-overflow.js
new file mode 100644 (file)
index 0000000..7e0ef54
--- /dev/null
@@ -0,0 +1,284 @@
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap nav-overflow.js
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ * --------------------------------------------------------------------------
+ */
+
+import BaseComponent from './base-component.js'
+import EventHandler from './dom/event-handler.js'
+import SelectorEngine from './dom/selector-engine.js'
+
+/**
+ * Constants
+ */
+
+const NAME = 'navoverflow'
+const DATA_KEY = 'bs.navoverflow'
+const EVENT_KEY = `.${DATA_KEY}`
+
+const EVENT_UPDATE = `update${EVENT_KEY}`
+const EVENT_OVERFLOW = `overflow${EVENT_KEY}`
+
+const CLASS_NAME_OVERFLOW = 'nav-overflow'
+const CLASS_NAME_OVERFLOW_MENU = 'nav-overflow-menu'
+const CLASS_NAME_HIDDEN = 'd-none'
+
+const SELECTOR_NAV_ITEM = '.nav-item'
+const SELECTOR_NAV_LINK = '.nav-link'
+const SELECTOR_OVERFLOW_TOGGLE = '.nav-overflow-toggle'
+const SELECTOR_OVERFLOW_MENU = '.nav-overflow-menu'
+const CLASS_NAME_KEEP = 'nav-overflow-keep'
+
+const Default = {
+  moreText: 'More',
+  moreIcon: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3m5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3m5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3"/></svg>',
+  threshold: 0 // Minimum items to keep visible before showing overflow
+}
+
+const DefaultType = {
+  moreText: 'string',
+  moreIcon: 'string',
+  threshold: 'number'
+}
+
+/**
+ * Class definition
+ */
+
+class NavOverflow extends BaseComponent {
+  constructor(element, config) {
+    super(element, config)
+
+    this._items = []
+    this._overflowItems = []
+    this._overflowMenu = null
+    this._overflowToggle = null
+    this._resizeObserver = null
+    this._isInitialized = false
+
+    this._init()
+  }
+
+  // Getters
+  static get Default() {
+    return Default
+  }
+
+  static get DefaultType() {
+    return DefaultType
+  }
+
+  static get NAME() {
+    return NAME
+  }
+
+  // Public
+  update() {
+    this._calculateOverflow()
+    EventHandler.trigger(this._element, EVENT_UPDATE)
+  }
+
+  dispose() {
+    if (this._resizeObserver) {
+      this._resizeObserver.disconnect()
+    }
+
+    // Move items back to original positions
+    this._restoreItems()
+
+    // Remove overflow menu
+    if (this._overflowToggle && this._overflowToggle.parentElement) {
+      this._overflowToggle.parentElement.remove()
+    }
+
+    super.dispose()
+  }
+
+  // Private
+  _init() {
+    // Add overflow class to nav
+    this._element.classList.add(CLASS_NAME_OVERFLOW)
+
+    // Get all nav items
+    this._items = [...SelectorEngine.find(SELECTOR_NAV_ITEM, this._element)]
+
+    // Store original order data
+    for (const [index, item] of this._items.entries()) {
+      item.dataset.bsNavOrder = index
+    }
+
+    // Create overflow dropdown if it doesn't exist
+    this._createOverflowMenu()
+
+    // Setup resize observer
+    this._setupResizeObserver()
+
+    // Initial calculation
+    this._calculateOverflow()
+
+    this._isInitialized = true
+  }
+
+  _createOverflowMenu() {
+    // Check if overflow menu already exists
+    this._overflowToggle = SelectorEngine.findOne(SELECTOR_OVERFLOW_TOGGLE, this._element)
+
+    if (this._overflowToggle) {
+      this._overflowMenu = SelectorEngine.findOne(SELECTOR_OVERFLOW_MENU, this._element)
+      return
+    }
+
+    // Create the overflow dropdown item
+    const overflowItem = document.createElement('li')
+    overflowItem.className = 'nav-item nav-overflow-item dropdown'
+    overflowItem.innerHTML = `
+      <button class="nav-link nav-overflow-toggle dropdown-toggle" type="button" data-bs-toggle="dropdown" data-bs-container="body" data-bs-strategy="fixed" aria-expanded="false">
+        <span class="nav-overflow-icon">${this._config.moreIcon}</span>
+        <span class="nav-overflow-text">${this._config.moreText}</span>
+      </button>
+      <ul class="${CLASS_NAME_OVERFLOW_MENU} dropdown-menu dropdown-menu-end"></ul>
+    `
+
+    this._element.append(overflowItem)
+    this._overflowToggle = overflowItem.querySelector(SELECTOR_OVERFLOW_TOGGLE)
+    this._overflowMenu = overflowItem.querySelector(SELECTOR_OVERFLOW_MENU)
+  }
+
+  _setupResizeObserver() {
+    if (typeof ResizeObserver === 'undefined') {
+      // Fallback for older browsers
+      EventHandler.on(window, 'resize', () => this._calculateOverflow())
+      return
+    }
+
+    this._resizeObserver = new ResizeObserver(() => {
+      this._calculateOverflow()
+    })
+
+    this._resizeObserver.observe(this._element)
+  }
+
+  _calculateOverflow() {
+    // First, restore all items to measure properly
+    this._restoreItems()
+
+    const navWidth = this._element.offsetWidth
+    const overflowItem = this._overflowToggle?.closest('.nav-item')
+    const overflowWidth = overflowItem?.offsetWidth || 0
+
+    let usedWidth = 0
+    const itemsToOverflow = []
+    const overflowThreshold = navWidth - overflowWidth - 10 // 10px buffer
+
+    // Calculate which items need to overflow (skip items with keep class)
+    for (const item of this._items) {
+      const itemWidth = item.offsetWidth
+      usedWidth += itemWidth
+
+      // Never overflow items with the keep class
+      if (item.classList.contains(CLASS_NAME_KEEP)) {
+        continue
+      }
+
+      if (usedWidth > overflowThreshold) {
+        itemsToOverflow.push(item)
+      }
+    }
+
+    // Check if we need threshold minimum visible
+    const visibleCount = this._items.length - itemsToOverflow.length
+    if (visibleCount < this._config.threshold && this._items.length > this._config.threshold) {
+      // Add more items to overflow until we reach threshold (but not keep items)
+      const toMove = this._items.slice(this._config.threshold).filter(item => !item.classList.contains(CLASS_NAME_KEEP))
+      itemsToOverflow.length = 0
+      itemsToOverflow.push(...toMove)
+    }
+
+    // Move items to overflow menu
+    this._moveToOverflow(itemsToOverflow)
+
+    // Show/hide overflow toggle
+    if (overflowItem) {
+      if (itemsToOverflow.length > 0) {
+        overflowItem.classList.remove(CLASS_NAME_HIDDEN)
+      } else {
+        overflowItem.classList.add(CLASS_NAME_HIDDEN)
+      }
+    }
+
+    // Trigger overflow event if items changed
+    if (itemsToOverflow.length > 0) {
+      EventHandler.trigger(this._element, EVENT_OVERFLOW, {
+        overflowCount: itemsToOverflow.length,
+        visibleCount: this._items.length - itemsToOverflow.length
+      })
+    }
+  }
+
+  _moveToOverflow(items) {
+    if (!this._overflowMenu) {
+      return
+    }
+
+    // Clear existing overflow items
+    this._overflowMenu.innerHTML = ''
+    this._overflowItems = []
+
+    for (const item of items) {
+      // Clone the nav link as a dropdown item
+      const link = SelectorEngine.findOne(SELECTOR_NAV_LINK, item)
+      if (!link) {
+        continue
+      }
+
+      const dropdownItem = document.createElement('li')
+      const clonedLink = link.cloneNode(true)
+      clonedLink.className = 'dropdown-item'
+
+      // Preserve active state
+      if (link.classList.contains('active')) {
+        clonedLink.classList.add('active')
+      }
+
+      // Preserve disabled state
+      if (link.classList.contains('disabled') || link.hasAttribute('disabled')) {
+        clonedLink.classList.add('disabled')
+      }
+
+      dropdownItem.append(clonedLink)
+      this._overflowMenu.append(dropdownItem)
+
+      // Hide original item
+      item.classList.add(CLASS_NAME_HIDDEN)
+      item.dataset.bsNavOverflow = 'true'
+
+      this._overflowItems.push(item)
+    }
+  }
+
+  _restoreItems() {
+    for (const item of this._items) {
+      item.classList.remove(CLASS_NAME_HIDDEN)
+      delete item.dataset.bsNavOverflow
+    }
+
+    if (this._overflowMenu) {
+      this._overflowMenu.innerHTML = ''
+    }
+
+    this._overflowItems = []
+  }
+}
+
+/**
+ * Data API implementation
+ */
+
+EventHandler.on(document, 'DOMContentLoaded', () => {
+  for (const element of SelectorEngine.find('[data-bs-toggle="nav-overflow"]')) {
+    NavOverflow.getOrCreateInstance(element)
+  }
+})
+
+export default NavOverflow
index 00462e1068a3dd9bc0592112cc0a2f3916eca649..9b79de40dc43fa05c383f2e8012190685f5593c9 100644 (file)
@@ -788,6 +788,142 @@ describe('Dropdown', () => {
         }, 10)
       })
     })
+
+    it('should move menu to body when container is set to body', () => {
+      return new Promise(resolve => {
+        fixtureEl.innerHTML = [
+          '<div class="dropdown">',
+          '  <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+          '  <div class="dropdown-menu">',
+          '    <a class="dropdown-item" href="#">Link</a>',
+          '  </div>',
+          '</div>'
+        ].join('')
+
+        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+        const dropdown = new Dropdown(btnDropdown, {
+          container: 'body'
+        })
+
+        btnDropdown.addEventListener('shown.bs.dropdown', () => {
+          expect(dropdownMenu.parentNode).toEqual(document.body)
+          resolve()
+        })
+
+        dropdown.show()
+      })
+    })
+
+    it('should move menu to body when container is set to true', () => {
+      return new Promise(resolve => {
+        fixtureEl.innerHTML = [
+          '<div class="dropdown">',
+          '  <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+          '  <div class="dropdown-menu">',
+          '    <a class="dropdown-item" href="#">Link</a>',
+          '  </div>',
+          '</div>'
+        ].join('')
+
+        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+        const dropdown = new Dropdown(btnDropdown, {
+          container: true
+        })
+
+        btnDropdown.addEventListener('shown.bs.dropdown', () => {
+          expect(dropdownMenu.parentNode).toEqual(document.body)
+          resolve()
+        })
+
+        dropdown.show()
+      })
+    })
+
+    it('should move menu to specified element when container is an element', () => {
+      return new Promise(resolve => {
+        fixtureEl.innerHTML = [
+          '<div id="custom-container"></div>',
+          '<div class="dropdown">',
+          '  <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+          '  <div class="dropdown-menu">',
+          '    <a class="dropdown-item" href="#">Link</a>',
+          '  </div>',
+          '</div>'
+        ].join('')
+
+        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+        const customContainer = fixtureEl.querySelector('#custom-container')
+        const dropdown = new Dropdown(btnDropdown, {
+          container: customContainer
+        })
+
+        btnDropdown.addEventListener('shown.bs.dropdown', () => {
+          expect(dropdownMenu.parentNode).toEqual(customContainer)
+          resolve()
+        })
+
+        dropdown.show()
+      })
+    })
+
+    it('should restore menu to original parent when hidden', () => {
+      return new Promise(resolve => {
+        fixtureEl.innerHTML = [
+          '<div class="dropdown">',
+          '  <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+          '  <div class="dropdown-menu">',
+          '    <a class="dropdown-item" href="#">Link</a>',
+          '  </div>',
+          '</div>'
+        ].join('')
+
+        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+        const originalParent = dropdownMenu.parentNode
+        const dropdown = new Dropdown(btnDropdown, {
+          container: 'body'
+        })
+
+        btnDropdown.addEventListener('shown.bs.dropdown', () => {
+          expect(dropdownMenu.parentNode).toEqual(document.body)
+          dropdown.hide()
+        })
+
+        btnDropdown.addEventListener('hidden.bs.dropdown', () => {
+          expect(dropdownMenu.parentNode).toEqual(originalParent)
+          resolve()
+        })
+
+        dropdown.show()
+      })
+    })
+
+    it('should work with container via data attribute', () => {
+      return new Promise(resolve => {
+        fixtureEl.innerHTML = [
+          '<div class="dropdown">',
+          '  <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-container="body">Dropdown</button>',
+          '  <div class="dropdown-menu">',
+          '    <a class="dropdown-item" href="#">Link</a>',
+          '  </div>',
+          '</div>'
+        ].join('')
+
+        const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+        const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
+        const dropdown = new Dropdown(btnDropdown)
+
+        btnDropdown.addEventListener('shown.bs.dropdown', () => {
+          expect(dropdownMenu.parentNode).toEqual(document.body)
+          resolve()
+        })
+
+        dropdown.show()
+      })
+    })
   })
 
   describe('hide', () => {
diff --git a/js/tests/unit/nav-overflow.spec.js b/js/tests/unit/nav-overflow.spec.js
new file mode 100644 (file)
index 0000000..2a93da7
--- /dev/null
@@ -0,0 +1,590 @@
+import NavOverflow from '../../src/nav-overflow.js'
+import { clearFixture, getFixture } from '../helpers/fixture.js'
+
+describe('NavOverflow', () => {
+  let fixtureEl
+
+  beforeAll(() => {
+    fixtureEl = getFixture()
+  })
+
+  afterEach(() => {
+    clearFixture()
+  })
+
+  describe('VERSION', () => {
+    it('should return plugin version', () => {
+      expect(NavOverflow.VERSION).toEqual(jasmine.any(String))
+    })
+  })
+
+  describe('Default', () => {
+    it('should return plugin default config', () => {
+      expect(NavOverflow.Default).toEqual(jasmine.any(Object))
+      expect(NavOverflow.Default.moreText).toEqual('More')
+      expect(NavOverflow.Default.threshold).toEqual(0)
+    })
+  })
+
+  describe('DATA_KEY', () => {
+    it('should return plugin data key', () => {
+      expect(NavOverflow.DATA_KEY).toEqual('bs.navoverflow')
+    })
+  })
+
+  describe('constructor', () => {
+    it('should take care of element either passed as a CSS selector or DOM element', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 2</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navBySelector = new NavOverflow('[data-bs-toggle="nav-overflow"]')
+      const navByElement = new NavOverflow(navEl)
+
+      expect(navBySelector._element).toEqual(navEl)
+      expect(navByElement._element).toEqual(navEl)
+
+      navBySelector.dispose()
+      navByElement.dispose()
+    })
+
+    it('should add nav-overflow class to element', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      expect(navEl).toHaveClass('nav-overflow')
+
+      navOverflow.dispose()
+    })
+
+    it('should create overflow menu toggle and dropdown', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      const toggle = navEl.querySelector('.nav-overflow-toggle')
+      const menu = navEl.querySelector('.nav-overflow-menu')
+
+      expect(toggle).not.toBeNull()
+      expect(menu).not.toBeNull()
+      expect(toggle).toHaveClass('dropdown-toggle')
+      expect(menu).toHaveClass('dropdown-menu')
+
+      navOverflow.dispose()
+    })
+
+    it('should store order data on nav items', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 2</a></li>',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 3</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+      const items = navEl.querySelectorAll('.nav-item:not(.nav-overflow-item)')
+
+      expect(items[0].dataset.bsNavOrder).toEqual('0')
+      expect(items[1].dataset.bsNavOrder).toEqual('1')
+      expect(items[2].dataset.bsNavOrder).toEqual('2')
+
+      navOverflow.dispose()
+    })
+
+    it('should respect custom moreText option', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl, {
+        moreText: 'See all'
+      })
+
+      const toggleText = navEl.querySelector('.nav-overflow-text')
+      expect(toggleText.textContent).toEqual('See all')
+
+      navOverflow.dispose()
+    })
+  })
+
+  describe('update', () => {
+    it('should trigger update event', () => {
+      return new Promise(resolve => {
+        fixtureEl.innerHTML = [
+          '<ul class="nav" data-bs-toggle="nav-overflow">',
+          '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+          '</ul>'
+        ].join('')
+
+        const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+        const navOverflow = new NavOverflow(navEl)
+
+        navEl.addEventListener('update.bs.navoverflow', () => {
+          navOverflow.dispose()
+          resolve()
+        })
+
+        navOverflow.update()
+      })
+    })
+  })
+
+  describe('getInstance', () => {
+    it('should return nav overflow instance', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      expect(NavOverflow.getInstance(navEl)).toEqual(navOverflow)
+      expect(NavOverflow.getInstance(navEl)).toBeInstanceOf(NavOverflow)
+
+      navOverflow.dispose()
+    })
+
+    it('should return null when there is no instance', () => {
+      fixtureEl.innerHTML = '<ul class="nav"></ul>'
+
+      const navEl = fixtureEl.querySelector('.nav')
+
+      expect(NavOverflow.getInstance(navEl)).toBeNull()
+    })
+  })
+
+  describe('getOrCreateInstance', () => {
+    it('should return nav overflow instance', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      expect(NavOverflow.getOrCreateInstance(navEl)).toEqual(navOverflow)
+      expect(NavOverflow.getInstance(navEl)).toEqual(NavOverflow.getOrCreateInstance(navEl, {}))
+      expect(NavOverflow.getOrCreateInstance(navEl)).toBeInstanceOf(NavOverflow)
+
+      navOverflow.dispose()
+    })
+
+    it('should return new instance when there is no instance', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('.nav')
+
+      expect(NavOverflow.getInstance(navEl)).toBeNull()
+
+      const instance = NavOverflow.getOrCreateInstance(navEl)
+      expect(instance).toBeInstanceOf(NavOverflow)
+
+      instance.dispose()
+    })
+  })
+
+  describe('overflow behavior', () => {
+    it('should use dropdown with container option for overflow menu', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      const toggle = navEl.querySelector('.nav-overflow-toggle')
+      expect(toggle.getAttribute('data-bs-container')).toEqual('body')
+      expect(toggle.getAttribute('data-bs-strategy')).toEqual('fixed')
+
+      navOverflow.dispose()
+    })
+
+    it('should preserve nav-overflow-keep items', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="width: 100px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item nav-overflow-keep"><a class="nav-link" href="#">Keep</a></li>',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 2</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+      const keepItem = navEl.querySelector('.nav-overflow-keep')
+
+      expect(keepItem).not.toHaveClass('d-none')
+
+      navOverflow.dispose()
+    })
+
+    it('should hide items that overflow the nav width', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="display: flex; width: 250px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 2</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 3</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 4</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 5</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      const hiddenItems = navEl.querySelectorAll('.nav-item[data-bs-nav-overflow="true"]')
+      expect(hiddenItems.length).toBeGreaterThan(0)
+
+      for (const item of hiddenItems) {
+        expect(item).toHaveClass('d-none')
+      }
+
+      navOverflow.dispose()
+    })
+
+    it('should show overflow toggle when items overflow', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="display: flex; width: 250px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 2</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 3</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 4</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      const overflowItem = navEl.querySelector('.nav-overflow-item')
+      expect(overflowItem).not.toHaveClass('d-none')
+
+      navOverflow.dispose()
+    })
+
+    it('should hide overflow toggle when no items overflow', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="display: flex; width: 5000px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item" style="flex: 0 0 50px; width: 50px;"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 50px; width: 50px;"><a class="nav-link" href="#">Link 2</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      const overflowItem = navEl.querySelector('.nav-overflow-item')
+      expect(overflowItem).toHaveClass('d-none')
+
+      navOverflow.dispose()
+    })
+
+    it('should clone overflowed items into the dropdown menu', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="display: flex; width: 250px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 2</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 3</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 4</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      const menu = navEl.querySelector('.nav-overflow-menu')
+      const dropdownItems = menu.querySelectorAll('.dropdown-item')
+      expect(dropdownItems.length).toBeGreaterThan(0)
+
+      navOverflow.dispose()
+    })
+
+    it('should preserve active state on cloned dropdown items', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="display: flex; width: 150px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link active" href="#">Active</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 3</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      const menu = navEl.querySelector('.nav-overflow-menu')
+      const activeDropdownItems = menu.querySelectorAll('.dropdown-item.active')
+      const originalActiveHidden = navEl.querySelector('.nav-item[data-bs-nav-overflow="true"] .nav-link.active')
+
+      if (originalActiveHidden) {
+        expect(activeDropdownItems.length).toBeGreaterThan(0)
+      }
+
+      navOverflow.dispose()
+    })
+
+    it('should preserve disabled state on cloned dropdown items', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="display: flex; width: 150px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link disabled" href="#">Disabled</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 3</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      const menu = navEl.querySelector('.nav-overflow-menu')
+      const disabledDropdownItems = menu.querySelectorAll('.dropdown-item.disabled')
+      const originalDisabledHidden = navEl.querySelector('.nav-item[data-bs-nav-overflow="true"] .nav-link.disabled')
+
+      if (originalDisabledHidden) {
+        expect(disabledDropdownItems.length).toBeGreaterThan(0)
+      }
+
+      navOverflow.dispose()
+    })
+
+    it('should skip items without a nav-link when moving to overflow', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="display: flex; width: 150px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><span>No link</span></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 3</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+
+      expect(() => {
+        const navOverflow = new NavOverflow(navEl)
+        navOverflow.dispose()
+      }).not.toThrow()
+    })
+
+    it('should fire overflow event with overflowCount and visibleCount', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="display: flex; width: 250px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 2</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 3</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 4</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 5</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      let eventFired = false
+      let receivedOverflowCount = 0
+      let receivedVisibleCount = 0
+
+      navEl.addEventListener('overflow.bs.navoverflow', event => {
+        eventFired = true
+        receivedOverflowCount = event.overflowCount
+        receivedVisibleCount = event.visibleCount
+      })
+
+      const navOverflow = new NavOverflow(navEl)
+
+      expect(eventFired).toBeTrue()
+      expect(receivedOverflowCount).toBeGreaterThan(0)
+      expect(receivedVisibleCount).toEqual(jasmine.any(Number))
+
+      navOverflow.dispose()
+    })
+
+    it('should restore items when update causes no overflow', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="display: flex; width: 250px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 2</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 3</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 4</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      const hiddenBefore = navEl.querySelectorAll('.nav-item[data-bs-nav-overflow="true"]')
+      expect(hiddenBefore.length).toBeGreaterThan(0)
+
+      // Widen the nav to remove overflow
+      navEl.style.width = '5000px'
+      navOverflow.update()
+
+      const hiddenAfter = navEl.querySelectorAll('.nav-item[data-bs-nav-overflow="true"]')
+      expect(hiddenAfter.length).toEqual(0)
+
+      navOverflow.dispose()
+    })
+
+    it('should reuse existing overflow toggle and menu from markup', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item dropdown">',
+        '    <button class="nav-link nav-overflow-toggle dropdown-toggle" type="button">More</button>',
+        '    <ul class="nav-overflow-menu dropdown-menu"></ul>',
+        '  </li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const existingToggle = navEl.querySelector('.nav-overflow-toggle')
+      const navOverflow = new NavOverflow(navEl)
+
+      // Should reuse the existing toggle, not create a new one
+      const toggles = navEl.querySelectorAll('.nav-overflow-toggle')
+      expect(toggles.length).toEqual(1)
+      expect(toggles[0]).toBe(existingToggle)
+
+      navOverflow.dispose()
+    })
+  })
+
+  describe('config', () => {
+    it('should respect custom moreIcon option', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const customIcon = '<span class="custom-icon">...</span>'
+      const navOverflow = new NavOverflow(navEl, {
+        moreIcon: customIcon
+      })
+
+      const iconContainer = navEl.querySelector('.nav-overflow-icon')
+      expect(iconContainer.innerHTML).toContain('custom-icon')
+
+      navOverflow.dispose()
+    })
+
+    it('should respect threshold option', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="display: flex; width: 150px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 2</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 3</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 4</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 5</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl, {
+        threshold: 2
+      })
+
+      const visibleItems = navEl.querySelectorAll('.nav-item:not(.nav-overflow-item):not(.d-none)')
+      expect(visibleItems.length).toBeGreaterThanOrEqual(2)
+
+      navOverflow.dispose()
+    })
+
+    it('should have correct DefaultType', () => {
+      expect(NavOverflow.DefaultType).toEqual(jasmine.objectContaining({
+        moreText: 'string',
+        moreIcon: 'string',
+        threshold: 'number'
+      }))
+    })
+  })
+
+  describe('dispose', () => {
+    it('should dispose nav overflow and remove overflow menu', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      expect(NavOverflow.getInstance(navEl)).not.toBeNull()
+      expect(navEl.querySelector('.nav-overflow-toggle')).not.toBeNull()
+
+      navOverflow.dispose()
+
+      expect(NavOverflow.getInstance(navEl)).toBeNull()
+      expect(navEl.querySelector('.nav-overflow-toggle')).toBeNull()
+    })
+
+    it('should disconnect ResizeObserver on dispose', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      const observer = navOverflow._resizeObserver
+      if (observer) {
+        spyOn(observer, 'disconnect').and.callThrough()
+        navOverflow.dispose()
+        expect(observer.disconnect).toHaveBeenCalled()
+      } else {
+        navOverflow.dispose()
+      }
+    })
+
+    it('should restore hidden items on dispose', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" style="display: flex; width: 250px;" data-bs-toggle="nav-overflow">',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 1</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 2</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 3</a></li>',
+        '  <li class="nav-item" style="flex: 0 0 100px; width: 100px;"><a class="nav-link" href="#">Link 4</a></li>',
+        '</ul>'
+      ].join('')
+
+      const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
+      const navOverflow = new NavOverflow(navEl)
+
+      // Verify some items are hidden
+      const hiddenBefore = navEl.querySelectorAll('.nav-item.d-none:not(.nav-overflow-item)')
+      expect(hiddenBefore.length).toBeGreaterThan(0)
+
+      navOverflow.dispose()
+
+      // After dispose, original items should be visible
+      const originalItems = navEl.querySelectorAll('.nav-item:not(.nav-overflow-item)')
+      for (const item of originalItems) {
+        expect(item).not.toHaveClass('d-none')
+      }
+    })
+  })
+})
index 662f8b23a3195c23009fc006a85c5a1dba7550b6..cba537aec2f8ff391d6c1a3d5a9357c3ec33b0a8 100644 (file)
 // stylelint-disable hue-degree-notation, @stylistic/number-leading-zero
 
+@use "sass:map";
+@use "functions" as *;
+@use "mixins/tokens" as *;
+
 // Easily convert colors to oklch() with https://oklch.com/
-$new-blue: oklch(60% 0.24 240) !default;
-$new-indigo: oklch(56% 0.26 288) !default;
-$new-violet: oklch(56% 0.24 300) !default;
-$new-purple: oklch(56% 0.24 320) !default;
-$new-pink: oklch(60% 0.22 4) !default;
-$new-red: oklch(60% 0.22 20) !default;
-$new-orange: oklch(70% 0.22 52) !default;
-$new-amber: oklch(79% 0.2 78) !default;
-$new-yellow: oklch(88% 0.24 88) !default;
-$new-lime: oklch(65% 0.24 135) !default;
-$new-green: oklch(64% 0.22 160) !default;
-$new-teal: oklch(68% 0.22 190) !default;
-$new-cyan: oklch(69% 0.22 220) !default;
-$new-brown: oklch(60% 0.12 54) !default;
-$new-gray: oklch(60% 0.02 245) !default;
-$new-pewter: oklch(65% 0.01 290) !default;
-
-$hues: (
-  "blue": $new-blue,
-  "indigo": $new-indigo,
-  "violet": $new-violet,
-  "purple": $new-purple,
-  "pink": $new-pink,
-  "red": $new-red,
-  "orange": $new-orange,
-  "amber": $new-amber,
-  "yellow": $new-yellow,
-  "lime": $new-lime,
-  "green": $new-green,
-  "teal": $new-teal,
-  "cyan": $new-cyan,
-  "brown": $new-brown,
-  "gray": $new-gray,
-  "pewter": $new-pewter
+
+$white: #fff !default;
+$black: #000 !default;
+
+// scss-docs-start colors-list
+$blue: oklch(60% 0.24 240) !default;
+$indigo: oklch(56% 0.26 288) !default;
+$violet: oklch(56% 0.24 300) !default;
+$purple: oklch(56% 0.24 320) !default;
+$pink: oklch(60% 0.22 4) !default;
+$red: oklch(60% 0.22 20) !default;
+$orange: oklch(70% 0.22 52) !default;
+$amber: oklch(79% 0.2 78) !default;
+$yellow: oklch(88% 0.24 88) !default;
+$lime: oklch(65% 0.24 135) !default;
+$green: oklch(64% 0.22 160) !default;
+$teal: oklch(68% 0.22 190) !default;
+$cyan: oklch(69% 0.22 220) !default;
+$brown: oklch(60% 0.12 54) !default;
+$gray: oklch(60% 0.02 245) !default;
+$pewter: oklch(65% 0.01 290) !default;
+// scss-docs-end colors-list
+
+// scss-docs-start colors-map
+$colors: () !default;
+
+// stylelint-disable-next-line scss/dollar-variable-default
+$colors: defaults(
+  (
+    "blue": $blue,
+    "indigo": $indigo,
+    "violet": $violet,
+    "purple": $purple,
+    "pink": $pink,
+    "red": $red,
+    "orange": $orange,
+    "amber": $amber,
+    "yellow": $yellow,
+    "lime": $lime,
+    "green": $green,
+    "teal": $teal,
+    "cyan": $cyan,
+    "brown": $brown,
+    "gray": $gray,
+    "pewter": $pewter,
+  ),
+  $colors
+);
+// scss-docs-end colors-map
+
+// scss-docs-start color-mix-options
+$color-mix-space: lab !default;
+$tint-color: var(--white) !default;
+$shade-color: var(--black) !default;
+
+$color-tints: (
+  "025": 94%,
+  "050": 90%,
+  "100": 80%,
+  "200": 60%,
+  "300": 40%,
+  "400": 20%,
 ) !default;
 
-:root {
-  @each $color, $hue in $hues {
-    --#{$color}-025: color-mix(in lab, #fff 94%, #{$hue});
-    --#{$color}-050: color-mix(in lab, #fff 90%, #{$hue});
-    --#{$color}-100: color-mix(in lab, #fff 80%, #{$hue});
-    --#{$color}-200: color-mix(in lab, #fff 60%, #{$hue});
-    --#{$color}-300: color-mix(in lab, #fff 40%, #{$hue});
-    --#{$color}-400: color-mix(in lab, #fff 20%, #{$hue});
-    --#{$color}-500: #{$hue};
-    --#{$color}-600: color-mix(in lab, #000 16%, #{$hue});
-    --#{$color}-700: color-mix(in lab, #000 32%, #{$hue});
-    --#{$color}-800: color-mix(in lab, #000 48%, #{$hue});
-    --#{$color}-900: color-mix(in lab, #000 64%, #{$hue});
-    --#{$color}-950: color-mix(in lab, #000 76%, #{$hue});
-    --#{$color}-975: color-mix(in lab, #000 88%, #{$hue});
+$color-shades: (
+  "600": 16%,
+  "700": 32%,
+  "800": 48%,
+  "900": 64%,
+  "950": 76%,
+  "975": 88%,
+) !default;
+// scss-docs-end color-mix-options
+
+// scss-docs-start color-tokens
+$color-tokens: () !default;
+
+$-color-defaults: () !default;
+@each $color, $value in $colors {
+  @each $stop, $percent in $color-tints {
+    $-color-defaults: map.set($-color-defaults, --#{$color}-#{$stop}, color-mix(in #{$color-mix-space}, #{$tint-color} #{$percent}, #{$value}));
+  }
+  $-color-defaults: map.set($-color-defaults, --#{$color}-500, #{$value});
+  @each $stop, $percent in $color-shades {
+    $-color-defaults: map.set($-color-defaults, --#{$color}-#{$stop}, color-mix(in #{$color-mix-space}, #{$shade-color} #{$percent}, #{$value}));
   }
 }
 
-$white: #fff !default;
-$black: #000 !default;
+// stylelint-disable-next-line scss/dollar-variable-default
+$color-tokens: defaults($-color-defaults, $color-tokens);
+// scss-docs-end color-tokens
+
+:root {
+  @include tokens($color-tokens);
+}
index 2afc8c72cff91ae0a7d95973b08d08b75fcb0904..95d496d0def4b720e08727d3a31c3d3c3eea0866 100644 (file)
@@ -30,6 +30,8 @@ $min-contrast-ratio:         4.5 !default;
 // scss-docs-start spacer-variables-maps
 $spacer: 1rem !default;
 $spacers: (
+  -2: $spacer * -.5,
+  -1: $spacer * -.25,
   0: 0,
   1: $spacer * .25,
   2: $spacer * .5,
index 9332ffefb5f2be0b991be9e38d32ef667224d5d0..56e3a6243bffc5e37c99b880a834a8caf6ba819f 100644 (file)
@@ -28,7 +28,8 @@ $dialog-tokens: defaults(
     --dialog-border-width: var(--border-width),
     --dialog-border-radius: var(--border-radius-lg),
     --dialog-box-shadow: var(--box-shadow-lg),
-    --dialog-backdrop-bg: rgba(0, 0, 0, .5),
+    --dialog-backdrop-bg: rgb(0 0 0 / 50%),
+    --dialog-backdrop-blur: 8px,
     --dialog-header-padding: 1rem,
     --dialog-header-border-color: var(--border-color),
     --dialog-header-border-width: var(--border-width),
@@ -80,6 +81,7 @@ $dialog-sizes: defaults(
     // Native backdrop styling via ::backdrop pseudo-element
     &::backdrop {
       background-color: var(--dialog-backdrop-bg);
+      backdrop-filter: blur(var(--dialog-backdrop-blur));
     }
 
     // Animation support using native [open] attribute
index ae4e1ac1dffe6af2a907d1b231a231bb41433057..0705c0c3069670fd5df243529678ea515180aed5 100644 (file)
   }
 }
 
-// Colors
-@function to-rgb($value) {
-  @return color.channel($value, "red"), color.channel($value, "green"), color.channel($value, "blue");
-}
-
-// stylelint-disable scss/dollar-variable-pattern
-@function rgba-css-var($identifier, $target) {
-  @if $identifier == "body" and $target == "bg" {
-    @return rgba(var(--#{$identifier}-bg-rgb), var(--#{$target}-opacity));
-  } @if $identifier == "body" and $target == "text" {
-    @return rgba(var(--#{$identifier}-color-rgb), var(--#{$target}-opacity));
-  } @else {
-    @return rgba(var(--#{$identifier}-rgb), var(--#{$target}-opacity));
-  }
-}
-
-@function map-loop($map, $func, $args...) {
-  $_map: ();
-
-  @each $key, $value in $map {
-    // allow to pass the $key and $value of the map as an function argument
-    $_args: ();
-    @each $arg in $args {
-      $resolved-arg: $arg;
-      @if $arg == "$key" {
-        $resolved-arg: $key;
-      } @else if $arg == "$value" {
-        $resolved-arg: $value;
-      }
-      $_args: list.append($_args, $resolved-arg);
-    }
-
-    $_map: map.merge($_map, ($key: meta.call(meta.get-function($func), $_args...)));
-  }
-
-  @return $_map;
-}
-// stylelint-enable scss/dollar-variable-pattern
-
-@function varify($list) {
-  $result: null;
-  @each $entry in $list {
-    $result: list.append($result, var(--#{$entry}), space);
-  }
-  @return $result;
-}
-
 // Internal Bootstrap function to turn maps into its negative variant.
 // It prefixes the keys with `n` and makes the value negative.
 @function negativify-map($map) {
@@ -253,20 +206,3 @@ $_luminance-list: .0008 .001 .0011 .0013 .0015 .0017 .002 .0022 .0025 .0027 .003
 @function opaque($background, $foreground) {
   @return color-mix(in srgb, rgba($foreground, 1), $background, color.opacity($foreground) * 100%);
 }
-
-// scss-docs-start color-functions
-// // Tint a color: mix a color with white
-@function tint-color($color, $weight) {
-  @return color.mix(white, $color, $weight);
-}
-
-// // Shade a color: mix a color with black
-@function shade-color($color, $weight) {
-  @return color.mix(black, $color, $weight);
-}
-
-// // Shade the color if the weight is positive, else tint it
-@function shift-color($color, $weight) {
-  @return if(sass($weight > 0): shade-color($color, $weight); else: tint-color($color, -$weight));
-}
-// scss-docs-end color-functions
diff --git a/scss/_nav-overflow.scss b/scss/_nav-overflow.scss
new file mode 100644 (file)
index 0000000..c423496
--- /dev/null
@@ -0,0 +1,27 @@
+// Nav Overflow (Priority+ Pattern)
+//
+// A responsive navigation pattern that automatically moves items
+// to an overflow dropdown when space is limited.
+
+@layer components {
+  .nav-overflow {
+    flex-wrap: nowrap;
+    min-width: 0; // Allow flex child to shrink below content width
+  }
+
+  // Container item for overflow
+  .nav-overflow-item {
+    flex-shrink: 0;
+    margin-inline-start: auto;
+  }
+
+  // Hide items that have been moved to overflow
+  .nav-overflow [data-bs-nav-overflow="true"] {
+    display: none;
+  }
+
+  // Preserve items that should never overflow
+  .nav-overflow-keep {
+    flex-shrink: 0;
+  }
+}
index 4feeaa48c217dd9719c8312d5d4e1869fa167362..20a53c216e3aba50b83e17012911a22a0ac070eb 100644 (file)
@@ -16,8 +16,8 @@ $nav-tokens: defaults(
     --nav-link-gap: .5rem,
     --nav-link-align: center,
     --nav-link-justify: center,
-    --nav-link-padding-x: 1rem,
-    --nav-link-padding-y: .5rem,
+    --nav-link-padding-x: .75rem,
+    --nav-link-padding-y: .375rem,
     --nav-link-color: var(--fg-2),
     --nav-link-hover-color: var(--fg-1),
     --nav-link-hover-bg: var(--bg-1),
@@ -97,7 +97,6 @@ $nav-underline-tokens: defaults(
 
   .nav-item {
     display: flex;
-
   }
 
   .nav-link {
@@ -109,6 +108,7 @@ $nav-underline-tokens: defaults(
     font-weight: var(--nav-link-font-weight);
     color: var(--nav-link-color);
     text-decoration: none;
+    white-space: nowrap;
     background: none;
     border: 0;
     @include border-radius(var(--border-radius));
index 19936b642136c5431b7176b8447a90de88c2476d..6021764cb2aa9a20686b63a31367b6d2bc5bda5e 100644 (file)
@@ -1,19 +1,17 @@
-@use "sass:map";
 @use "config" as *;
 @use "functions" as *;
-@use "variables" as *;
 @use "layout/breakpoints" as *;
-@use "mixins/border-radius" as *;
 @use "mixins/box-shadow" as *;
-@use "mixins/color-mode" as *;
-@use "mixins/focus-ring" as *;
 @use "mixins/gradients" as *;
 @use "mixins/tokens" as *;
 @use "mixins/transition" as *;
 
 // mdo-do: fix nav-link-height and navbar-brand-height, which we previously calculated with font-size, line-height, and block padding
 
-// stylelint-disable custom-property-no-missing-var-function
+// scss-docs-start navbar-breakpoints
+$navbar-breakpoints: $grid-breakpoints !default;
+// scss-docs-end navbar-breakpoints
+
 $navbar-tokens: () !default;
 
 // scss-docs-start navbar-tokens
@@ -21,21 +19,22 @@ $navbar-tokens: () !default;
 $navbar-tokens: defaults(
   (
     --navbar-padding-x: 0,
-    --navbar-padding-y: #{$spacer * .5},
+    --navbar-padding-y: .5rem,
     --navbar-color: var(--fg-2),
     --navbar-hover-color: var(--fg-1),
     --navbar-disabled-color: var(--fg-3),
-    --navbar-active-color: var(--fg),
+    --navbar-active-color: var(--fg-body),
     --navbar-brand-padding-y: .75rem,
     --navbar-brand-margin-end: 1rem,
-    --navbar-brand-font-size: var(--font-size-lg),
-    --navbar-brand-color: var(--fg),
-    --navbar-brand-hover-color: var(--fg),
+    --navbar-brand-font-size: var(--font-size-md),
+    --navbar-brand-font-weight: var(--font-weight-medium),
+    --navbar-brand-color: var(--fg-body),
+    --navbar-brand-hover-color: var(--fg-body),
     --navbar-nav-link-padding-x: .75rem,
+    --navbar-toggler-width: 2rem,
     --navbar-toggler-padding-y: .25rem,
     --navbar-toggler-padding-x: .75rem,
     --navbar-toggler-font-size: var(--font-size-lg),
-    --navbar-toggler-icon-bg: #{escape-svg(url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{color-mix(in oklch, var(--body-color) 75%, transparent)}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>"))},
     --navbar-toggler-border-color: color-mix(in oklch, var(--fg-body) 15%, transparent),
     --navbar-toggler-border-radius: var(--border-radius),
     --navbar-toggler-transition: box-shadow .15s ease-in-out,
@@ -57,28 +56,26 @@ $navbar-dark-tokens: defaults(
     --navbar-brand-color: var(--white),
     --navbar-brand-hover-color: var(--white),
     --navbar-toggler-border-color: color-mix(in oklch, var(--white) .1, transparent),
-    --navbar-toggler-icon-bg: #{escape-svg(url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='rgba(255, 255, 255, .55)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>"))},
   ),
   $navbar-dark-tokens
 );
 // scss-docs-end navbar-dark-tokens
-// stylelint-enable custom-property-no-missing-var-function
 
 @layer components {
+  // Base navbar
   .navbar {
     @include tokens($navbar-tokens);
 
     position: relative;
     display: flex;
-    flex-wrap: wrap; // allow us to do the line break for collapsing content
+    flex-wrap: wrap;
     align-items: center;
-    justify-content: space-between; // space out brand from logo
+    justify-content: space-between;
     padding: var(--navbar-padding-y) var(--navbar-padding-x);
+    @include set-container();
     @include gradient-bg();
 
-    // Because flex properties aren't inherited, we need to redeclare these first
-    // few properties so that content nested within behave properly.
-    // The `flex-wrap` property is inherited to simplify the expanded navbars
+    // Container properties for nested containers
     %container-flex-properties {
       display: flex;
       flex-wrap: inherit;
@@ -101,12 +98,12 @@ $navbar-dark-tokens: defaults(
   // Navbar brand
   //
   // Used for brand, project, or site names.
-
   .navbar-brand {
     padding-top: var(--navbar-brand-padding-y);
     padding-bottom: var(--navbar-brand-padding-y);
     margin-inline-end: var(--navbar-brand-margin-end);
     font-size: var(--navbar-brand-font-size);
+    font-weight: var(--navbar-brand-font-weight);
     color: var(--navbar-brand-color);
     text-decoration: none;
     white-space: nowrap;
@@ -117,24 +114,28 @@ $navbar-dark-tokens: defaults(
     }
   }
 
-  // Navbar nav
+  // Navigation within navbars. Sets all nav-link CSS variables needed for
+  // proper styling.
   //
-  // Custom navbar navigation (doesn't require `.nav`, but does make use of `.nav-link`).
-
+  // Relies on `.nav` base class.
   .navbar-nav {
     // scss-docs-start navbar-nav-css-vars
-    // --nav-link-padding-x: 0;
-    // @mdo-do: fix this, navbar shouldn't need to reuse nav link variables mb? or we need to bring them in…
-    // --nav-link-padding-y: #{$nav-link-padding-y};
-    // @include rfs($nav-link-font-size, --nav-link-font-size);
-    // --nav-link-font-weight: #{$nav-link-font-weight};
+    // Set all nav-link variables for self-contained styling
+    --nav-gap: .25rem;
+    --nav-link-gap: .5rem;
+    --nav-link-padding-x: .5rem;
+    --nav-link-padding-y: .5rem;
     --nav-link-color: var(--navbar-color);
     --nav-link-hover-color: var(--navbar-hover-color);
+    --nav-link-hover-bg: transparent;
+    --nav-link-active-color: var(--navbar-active-color);
+    --nav-link-active-bg: transparent;
     --nav-link-disabled-color: var(--navbar-disabled-color);
     // scss-docs-end navbar-nav-css-vars
 
     display: flex;
-    flex-direction: column; // cannot use `inherit` to get the `.navbar`s value
+    flex-direction: column;
+    gap: var(--nav-gap);
     padding-inline-start: 0;
     margin-bottom: 0;
     list-style: none;
@@ -145,170 +146,105 @@ $navbar-dark-tokens: defaults(
         color: var(--navbar-active-color);
       }
     }
-
-    // .dropdown-menu {
-    //   position: static;
-    // }
   }
 
   // Navbar text
   //
-  //
-
+  // For adding text or inline elements to the navbar
   .navbar-text {
-    // @mdo-do: fix this too
-    // padding-top: $nav-link-padding-y;
-    // padding-bottom: $nav-link-padding-y;
+    padding-top: var(--navbar-brand-padding-y);
+    padding-bottom: var(--navbar-brand-padding-y);
     color: var(--navbar-color);
 
     a,
     a:hover,
-    a:focus  {
+    a:focus {
       color: var(--navbar-active-color);
     }
   }
 
-  // Responsive navbar
-  //
-  // Custom styles for responsive collapsing and toggling of navbar contents.
-  // Powered by the collapse Bootstrap JavaScript plugin.
-
-  // When collapsed, prevent the toggleable navbar contents from appearing in
-  // the default flexbox row orientation. Requires the use of `flex-wrap: wrap`
-  // on the `.navbar` parent.
-  .navbar-collapse {
-    flex-grow: 1;
-    flex-basis: 100%;
-    // For always expanded or extra full navbars, ensure content aligns itself
-    // properly vertically. Can be easily overridden with flex utilities.
-    align-items: center;
-  }
-
   // Button for toggling the navbar when in its collapsed state
   .navbar-toggler {
-    padding: var(--navbar-toggler-padding-y) var(--navbar-toggler-padding-x);
-    font-size: var(--navbar-toggler-font-size);
-    line-height: 1;
-    color: var(--navbar-color);
-    background-color: transparent; // remove default button style
-    border: var(--border-width) solid var(--navbar-toggler-border-color); // remove default button style
-    @include border-radius(var(--navbar-toggler-border-radius));
-    @include transition(var(--navbar-toggler-transition));
-
-    &:hover {
-      text-decoration: none;
-    }
-
-    &:focus-visible {
-      text-decoration: none;
-      @include focus-ring(true);
-    }
-  }
-
-  // Keep as a separate element so folks can easily override it with another icon
-  // or image file as needed.
-  .navbar-toggler-icon {
-    display: inline-block;
-    width: 1.5em;
-    height: 1.5em;
-    vertical-align: middle;
-    background-image: var(--navbar-toggler-icon-bg);
-    background-repeat: no-repeat;
-    background-position: center;
-    background-size: 100%;
-  }
-
-  .navbar-nav-scroll {
-    max-height: var(--scroll-height, 75vh);
-    overflow-y: auto;
+    --btn-bg: transparent;
+    --btn-hover-bg: var(--bg-2);
   }
 
   // scss-docs-start navbar-expand-loop
   // Generate series of `.navbar-expand-*` responsive classes for configuring
-  // where your navbar collapses.
-  .navbar-expand {
-    @each $breakpoint in map.keys($grid-breakpoints) {
-      $next: breakpoint-next($breakpoint, $grid-breakpoints);
-      $infix: breakpoint-infix($next, $grid-breakpoints);
-
-      // stylelint-disable-next-line scss/selector-no-union-class-name
-      &#{$infix} {
-        @include media-breakpoint-up($next) {
-          flex-wrap: nowrap;
-          justify-content: flex-start;
+  // where your navbar collapses and expands. Uses container queries so the
+  // navbar responds to its own width, not the viewport width.
 
-          .navbar-nav {
-            --nav-link-padding-x: var(--navbar-nav-link-padding-x);
-            flex-direction: row;
-
-            // .dropdown-menu {
-            //   position: absolute;
-            // }
+  // Mixin for expanded state styles (applied to descendants)
+  @mixin navbar-expanded {
+    // Style the inner container since we can't style .navbar itself with container queries
+    > .container,
+    > .container-fluid,
+    %navbar-expand-container {
+      flex-wrap: nowrap;
+      justify-content: flex-start;
+    }
 
-            // .nav-link {
-            //   padding-inline: var(--navbar-nav-link-padding-x);
-            // }
-          }
+    .navbar-nav {
+      --nav-link-padding-x: var(--navbar-nav-link-padding-x);
+      flex-direction: row;
+    }
 
-          .navbar-nav-scroll {
-            overflow: visible;
-          }
+    .navbar-toggler {
+      display: none !important; // stylelint-disable-line declaration-no-important
+    }
 
-          .navbar-collapse {
-            display: flex !important; // stylelint-disable-line declaration-no-important
-            flex-basis: auto;
-          }
+    .offcanvas {
+      // stylelint-disable declaration-no-important
+      position: static;
+      z-index: auto;
+      flex-grow: 1;
+      width: auto !important;
+      height: auto !important;
+      visibility: visible !important;
+      background-color: transparent !important;
+      border: 0 !important;
+      transform: none !important;
+      @include box-shadow(none);
+      @include transition(none);
+      // stylelint-enable declaration-no-important
+
+      .offcanvas-header {
+        display: none;
+      }
 
-          .navbar-toggler {
-            display: none !important; // stylelint-disable-line declaration-no-important
-          }
+      .offcanvas-body {
+        display: flex;
+        flex-grow: 0;
+        flex-direction: row;
+        align-items: center;
+        padding: 0;
+        overflow-y: visible;
+      }
+    }
+  }
 
-          .offcanvas {
-            // stylelint-disable declaration-no-important
-            position: static;
-            z-index: auto;
-            flex-grow: 1;
-            width: auto !important;
-            height: auto !important;
-            visibility: visible !important;
-            background-color: transparent !important;
-            border: 0 !important;
-            transform: none !important;
-            @include box-shadow(none);
-            @include transition(none);
-            // stylelint-enable declaration-no-important
+  // Always expanded (no responsive behavior)
+  .navbar-expand {
+    @include navbar-expanded();
 
-            .offcanvas-header {
-              display: none;
-            }
+    // Also set on navbar itself for non-responsive case
+    flex-wrap: nowrap;
+    justify-content: flex-start;
+  }
 
-            .offcanvas-body {
-              display: flex;
-              flex-grow: 0;
-              padding: 0;
-              overflow-y: visible;
-            }
-          }
+  // Responsive navbar expand classes using container queries
+  @include loop-breakpoints-down($navbar-breakpoints) using ($breakpoint, $next, $infix) {
+    @if $next {
+      .navbar-expand#{$infix} {
+        @include container-breakpoint-up($next) {
+          @include navbar-expanded();
         }
       }
     }
   }
   // scss-docs-end navbar-expand-loop
 
-  // Navbar themes
-  //
-  // Styles for switching between navbars with light or dark background.
-
-  .navbar-dark,
   .navbar[data-bs-theme="dark"] {
     @include tokens($navbar-dark-tokens);
   }
-
-  @if $enable-dark-mode {
-    @include color-mode(dark) {
-      .navbar-toggler-icon {
-        --navbar-toggler-icon-bg: #{escape-svg(url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='rgba(255, 255, 255, .55)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>"))};
-      }
-    }
-  }
 }
index ce13e2198682a0a9dda9a85fc14d746c1a71e481..6d889ec281b0cbab663f1a8b742aa9c1e0f31fb5 100644 (file)
@@ -1,7 +1,6 @@
-@use "sass:map";
-@use "config" as *;
 @use "functions" as *;
 @use "variables" as *;
+@use "mixins/border-radius" as *;
 @use "mixins/box-shadow" as *;
 @use "mixins/transition" as *;
 @use "mixins/backdrop" as *;
@@ -15,15 +14,17 @@ $offcanvas-tokens: () !default;
 // stylelint-disable-next-line scss/dollar-variable-default
 $offcanvas-tokens: defaults(
   (
+    --offcanvas-inset: var(--spacer),
     --offcanvas-zindex: #{$zindex-offcanvas},
     --offcanvas-width: 400px,
     --offcanvas-height: 30vh,
-    --offcanvas-padding-x: 1rem,
-    --offcanvas-padding-y: 1rem,
+    --offcanvas-padding-x: var(--spacer),
+    --offcanvas-padding-y: var(--spacer),
     --offcanvas-color: var(--fg-body),
     --offcanvas-bg: var(--bg-body),
     --offcanvas-border-width: var(--border-width),
     --offcanvas-border-color: var(--border-color-translucent),
+    --offcanvas-border-radius: var(--border-radius-lg),
     --offcanvas-box-shadow: var(--box-shadow-lg),
     --offcanvas-transition: transform .3s ease-in-out,
     --offcanvas-title-line-height: 1.5,
@@ -39,8 +40,9 @@ $offcanvas-backdrop-tokens: () !default;
 // stylelint-disable-next-line scss/dollar-variable-default
 $offcanvas-backdrop-tokens: defaults(
   (
-    bg: rgba(0, 0, 0, .5),
-    opacity: .5,
+    --offcanvas-backdrop-bg: var(--bg-body),
+    --offcanvas-backdrop-opacity: 25%,
+    --offcanvas-backdrop-blur: 8px,
   ),
   $offcanvas-backdrop-tokens
 );
@@ -51,40 +53,38 @@ $offcanvas-backdrop-tokens: defaults(
 }
 
 @layer components {
-  @each $breakpoint in map.keys($grid-breakpoints) {
-    $next: breakpoint-next($breakpoint, $grid-breakpoints);
-    $infix: breakpoint-infix($next, $grid-breakpoints);
-
+  // Apply CSS vars to all offcanvas responsive variants
+  @include loop-breakpoints-down() using ($breakpoint, $next, $infix) {
     .offcanvas#{$infix} {
       @extend %offcanvas-css-vars;
     }
   }
 
-  @each $breakpoint in map.keys($grid-breakpoints) {
-    $next: breakpoint-next($breakpoint, $grid-breakpoints);
-    $infix: breakpoint-infix($next, $grid-breakpoints);
-
+  // Responsive offcanvas styles
+  @include loop-breakpoints-down() using ($breakpoint, $next, $infix) {
     .offcanvas#{$infix} {
       @include media-breakpoint-down($next) {
         position: fixed;
-        bottom: 0;
         z-index: var(--offcanvas-zindex);
         display: flex;
         flex-direction: column;
-        max-width: 100%;
+        max-width: calc(100% - var(--offcanvas-inset) * 2);
+        max-height: calc(100% - var(--offcanvas-inset) * 2);
         color: var(--offcanvas-color);
         visibility: hidden;
         background-color: var(--offcanvas-bg);
         background-clip: padding-box;
+        border: var(--offcanvas-border-width) solid var(--offcanvas-border-color);
+        @include border-radius(var(--offcanvas-border-radius));
         outline: 0;
         @include box-shadow(var(--offcanvas-box-shadow));
         @include transition(var(--offcanvas-transition));
 
+        // Placement: Start (left in LTR, right in RTL)
         &.offcanvas-start {
-          inset-block: 0;
-          inset-inline-start: 0;
+          inset-block: var(--offcanvas-inset);
+          inset-inline-start: var(--offcanvas-inset);
           width: var(--offcanvas-width);
-          border-inline-end: var(--offcanvas-border-width) solid var(--offcanvas-border-color);
           transform: translateX(-100%);
 
           :root:dir(rtl) & {
@@ -92,11 +92,11 @@ $offcanvas-backdrop-tokens: defaults(
           }
         }
 
+        // Placement: End (right in LTR, left in RTL)
         &.offcanvas-end {
-          inset-block: 0;
-          inset-inline-end: 0;
+          inset-block: var(--offcanvas-inset);
+          inset-inline-end: var(--offcanvas-inset);
           width: var(--offcanvas-width);
-          border-inline-start: var(--offcanvas-border-width) solid var(--offcanvas-border-color);
           transform: translateX(100%);
 
           :root:dir(rtl) & {
@@ -104,22 +104,31 @@ $offcanvas-backdrop-tokens: defaults(
           }
         }
 
+        // Placement: Top
         &.offcanvas-top {
-          inset: 0 0 auto;
+          inset: var(--offcanvas-inset) var(--offcanvas-inset) auto;
           height: var(--offcanvas-height);
-          max-height: 100%;
-          border-block-end: var(--offcanvas-border-width) solid var(--offcanvas-border-color);
           transform: translateY(-100%);
         }
 
+        // Placement: Bottom
         &.offcanvas-bottom {
-          inset: auto 0 0;
+          inset: auto var(--offcanvas-inset) var(--offcanvas-inset);
           height: var(--offcanvas-height);
-          max-height: 100%;
-          border-block-start: var(--offcanvas-border-width) solid var(--offcanvas-border-color);
           transform: translateY(100%);
         }
 
+        // Fullscreen variant - covers entire viewport
+        &.offcanvas-fullscreen {
+          inset: var(--offcanvas-inset);
+          width: auto;
+          max-width: none;
+          height: auto;
+          max-height: none;
+          transform: translateY(100%);
+        }
+
+        // Show/hide states
         &.showing,
         &.show:not(.hiding) {
           transform: none;
@@ -132,6 +141,7 @@ $offcanvas-backdrop-tokens: defaults(
         }
       }
 
+      // Above breakpoint - show content inline (for responsive offcanvas)
       @if not ($infix == "") {
         @include media-breakpoint-up($next) {
           --offcanvas-height: auto;
@@ -143,11 +153,10 @@ $offcanvas-backdrop-tokens: defaults(
           }
 
           .offcanvas-body {
-            display: flex;
             flex-grow: 0;
+            flex-direction: row;
             padding: 0;
             overflow-y: visible;
-            // Reset `background-color` in case `.bg-*` classes are used in offcanvas
             background-color: transparent !important; // stylelint-disable-line declaration-no-important
           }
         }
@@ -155,10 +164,13 @@ $offcanvas-backdrop-tokens: defaults(
     }
   }
 
+  // Backdrop overlay
   .offcanvas-backdrop {
-    @include overlay-backdrop($zindex-offcanvas-backdrop, map.get($offcanvas-backdrop-tokens, bg), map.get($offcanvas-backdrop-tokens, opacity));
+    @include tokens($offcanvas-backdrop-tokens);
+    @include overlay-backdrop($zindex-offcanvas-backdrop, var(--offcanvas-backdrop-bg), var(--offcanvas-backdrop-opacity), var(--offcanvas-backdrop-blur));
   }
 
+  // Header with close button
   .offcanvas-header {
     display: flex;
     align-items: center;
@@ -166,7 +178,6 @@ $offcanvas-backdrop-tokens: defaults(
 
     .btn-close {
       padding: calc(var(--offcanvas-padding-y) * .5) calc(var(--offcanvas-padding-x) * .5);
-      // Split properties to avoid invalid calc() function if value is 0
       margin-inline-start: auto;
       margin-inline-end: calc(-.5 * var(--offcanvas-padding-x));
       margin-top: calc(-.5 * var(--offcanvas-padding-y));
@@ -174,14 +185,31 @@ $offcanvas-backdrop-tokens: defaults(
     }
   }
 
+  // Title
   .offcanvas-title {
     margin-bottom: 0;
     line-height: var(--offcanvas-title-line-height);
   }
 
+  // Scrollable body
   .offcanvas-body {
+    display: flex;
     flex-grow: 1;
+    flex-direction: column;
+    gap: var(--offcanvas-padding-y);
     padding: var(--offcanvas-padding-y) var(--offcanvas-padding-x);
     overflow-y: auto;
   }
+
+  // Optional footer
+  .offcanvas-footer {
+    display: flex;
+    flex-shrink: 0;
+    flex-wrap: wrap;
+    gap: .5rem;
+    align-items: center;
+    justify-content: flex-end;
+    padding: var(--offcanvas-padding-y) var(--offcanvas-padding-x);
+    border-block-start: var(--offcanvas-border-width) solid var(--offcanvas-border-color);
+  }
 }
index 294e170c6bb3a609ade8c3d85ecbc9f1c0d96d64..256e8139def4209733f4cb2ae353376655342fba 100644 (file)
@@ -47,6 +47,17 @@ $root-tokens: defaults(
     --line-height-6xl: 1,
     // scss-docs-end root-font-size-variables
 
+    // scss-docs-start root-font-weight-variables
+    --font-weight-lighter: lighter,
+    --font-weight-light: 300,
+    --font-weight-normal: 400,
+    --font-weight-medium: 500,
+    --font-weight-semibold: 600,
+    --font-weight-bold: 700,
+    --font-weight-bolder: bolder,
+    // scss-docs-end root-font-weight-variables
+
+    // scss-docs-start root-body-variables
     --body-font-family: system-ui,
     --body-font-size: var(--font-size-base),
     --body-font-weight: #{$font-weight-base},
@@ -98,8 +109,6 @@ $root-tokens: defaults(
     // scss-docs-end root-focus-variables
 
     // scss-docs-start root-form-variables
-    --control-bg: var(--bg-body),
-    --control-fg: var(--fg-body),
     --control-checked-bg: var(--primary-base),
     --control-checked-border-color: var(--control-checked-bg),
     --control-active-bg: var(--primary-base),
@@ -107,6 +116,9 @@ $root-tokens: defaults(
     --control-disabled-bg: var(--bg-3),
     --control-disabled-opacity: .65,
 
+    --btn-input-fg: var(--fg-body),
+    --btn-input-bg: var(--bg-body),
+
     --btn-input-min-height: 2.5rem,
     --btn-input-padding-y: .375rem,
     --btn-input-padding-x: .75rem,
@@ -177,3 +189,11 @@ $root-tokens: defaults(
 
   color-scheme: light dark;
 }
+
+[data-bs-theme="dark"] {
+  color-scheme: dark;
+}
+
+[data-bs-theme="light"] {
+  color-scheme: light;
+}
index 552d0ddc9c3168670684fd78b9d8980d8f6c280a..eba2a97f52e353be0a3d99ebc08323c58eb8bc13 100644 (file)
@@ -1,4 +1,3 @@
-@use "colors" as *;
 @use "config" as *;
 @use "functions" as *;
 
@@ -21,7 +20,7 @@ $escaped-characters: (
 // The gradient which is added to components if `$enable-gradients` is `true`
 // This gradient is also added to elements with `.bg-gradient`
 // scss-docs-start variable-gradient
-$gradient: linear-gradient(180deg, rgba($white, .15), rgba($white, 0)) !default;
+$gradient: linear-gradient(180deg, color-mix(var(--white) 15%, transparent), color-mix(var(--white) 0%, transparent)) !default;
 // scss-docs-end variable-gradient
 
 // Spacing
@@ -67,9 +66,6 @@ $position-values: (
 // $link-color:                              var !default;
 $link-decoration:                         underline !default;
 $link-underline-offset:                   .2em !default;
-$link-shade-percentage:                   20% !default;
-// $link-hover-color:                        shift-color($link-color, $link-shade-percentage) !default;
-// $link-hover-decoration:                   $link-decoration !default;
 
 $stretched-link-pseudo-element:           after !default;
 $stretched-link-z-index:                  1 !default;
index 95ada9554ab55b14f92814ae4cb4e5640aab0bf5..aebbd7c8992b14da4431726e222d8d87dc35d618 100644 (file)
@@ -26,6 +26,7 @@
 @forward "dropdown";
 @forward "list-group";
 @forward "nav";
+@forward "nav-overflow";
 @forward "navbar";
 @forward "offcanvas";
 @forward "pagination";
index c6e547325c7f836dd5b96bf1a8df9c0b327cb383..db80d673309b5bbb619c2ba9a4491782b0642471 100644 (file)
@@ -238,7 +238,7 @@ $reboot-mark-tokens: defaults(
 
   small,
   .small {
-    font-size: var(--small-font-size, var(--font-size-sm));
+    font-size: var(--small-font-size, 87.5%);
   }
 
   // Mark
index b6798093a947cbfc66cad32f2f9d57f05a0ebfef..3744c2fc265af6cb5952661941d1d7f39242df4b 100644 (file)
@@ -13,8 +13,8 @@ $chip-input-tokens: defaults(
     --chip-input-padding-x: .75rem,
     --chip-input-gap: .375rem,
     --chip-input-ghost-min-width: 5rem,
-    --control-color: var(--fg-body),
-    --control-bg: var(--bg-body),
+    --control-fg: var(--btn-input-fg),
+    --control-bg: var(--btn-input-bg),
     --control-border-width: var(--border-width),
     --control-border-color: var(--border-color),
     --control-border-radius: var(--border-radius),
@@ -34,7 +34,7 @@ $chip-input-tokens: defaults(
     align-items: center;
     padding: var(--chip-input-padding-y) var(--chip-input-padding-x);
 
-    color: var(--control-color);
+    color: var(--control-fg);
     background-color: var(--control-bg);
     border: var(--control-border-width) solid var(--control-border-color);
     @include border-radius(var(--control-border-radius), 0);
index 134160e2f1884989abab6e4f8df1a5baf9940720..17a2bdf6fb13d0ddec31c6175c8815f3d3f54aac 100644 (file)
@@ -18,8 +18,8 @@ $form-control-tokens: defaults(
     --control-padding-x: var(--btn-input-padding-x),
     --control-font-size: var(--btn-input-font-size),
     --control-line-height: var(--btn-input-line-height),
-    --control-color: var(--control-fg),
-    --control-bg: var(--control-bg),
+    --control-fg: var(--btn-input-fg),
+    --control-bg: var(--btn-input-bg),
     --control-border-width: var(--border-width),
     --control-border-color: var(--border-color),
     --control-border-radius: var(--border-radius),
@@ -30,7 +30,7 @@ $form-control-tokens: defaults(
     --control-transition-timing: .15s ease-in-out,
     --control-transition: var(--control-transition-property) var(--control-transition-timing),
     --control-placeholder-color: var(--fg-3),
-    --control-disabled-color: var(--control-color),
+    --control-disabled-color: var(--control-fg),
     --control-disabled-bg: var(--bg-2),
     --control-disabled-border-color: var(--control-border-color),
     --control-select-bg: #{escape-svg(url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#00000080' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>"))},
@@ -62,7 +62,7 @@ $form-control-sizes: defaults(
     padding: var(--control-padding-y) var(--control-padding-x);
     font-size: var(--control-font-size);
     line-height: var(--control-line-height);
-    color: var(--control-color);
+    color: var(--control-fg);
     appearance: none;
     background-color: var(--control-bg);
     background-clip: padding-box;
@@ -146,7 +146,7 @@ $form-control-sizes: defaults(
       padding: var(--control-padding-y) var(--control-padding-x);
       margin: calc(var(--control-padding-y) * -1) calc(var(--control-padding-x) * -1);
       margin-inline-end: var(--control-padding-x);
-      color: var(--control-color);
+      color: var(--control-fg);
       @include gradient-bg(var(--control-action-bg));
       pointer-events: none;
       border-color: inherit;
@@ -173,7 +173,7 @@ $form-control-sizes: defaults(
     padding: var(--control-padding-y) 0;
     margin-bottom: 0; // match inputs if this class comes on inputs with default margins
     line-height: var(--control-line-height);
-    color: var(--control-color);
+    color: var(--control-fg);
     background-color: transparent;
     border: solid transparent;
     border-width: var(--control-border-width) 0;
index a9a4cd9053a8ba2b742fa6319ab51edca74daf1b..73a1989a893834364e3bf3131e04a2f177ce34a3 100644 (file)
@@ -22,7 +22,7 @@ $form-label-tokens: defaults(
     font-size: var(--label-font-size, var(--font-size-sm));
     font-style: var(--label-font-style, inherit);
     font-weight: var(--label-font-weight, inherit);
-    color: var(--label-color, inherit);
+    color: var(--label-color, var(--fg-body));
   }
 
   .form-label {
index 67c83c3eb63a9c80c847b2bffbb388ec1dbfbb8f..172fb1e531f17f9f6f54400dad1337ccc5e7b66d 100644 (file)
@@ -1,28 +1,33 @@
-// stylelint-disable selector-no-qualifying-type
+@use "../layout/breakpoints" as *;
 
 @layer helpers {
   // scss-docs-start stacks
-  .hstack,
-  b-hstack {
-    display: flex;
-    flex-direction: row;
-    align-items: center;
-    align-self: stretch;
+  .stack-container {
+    @include set-container();
   }
-  .hstack-start,
-  b-hstack[align="start"] {
+
+  [class*="hstack"],
+  [class*="vstack"] {
     display: flex;
-    flex-direction: row;
-    align-items: flex-start;
-    align-self: stretch;
+    flex: var(--stack-flex, 1 1 auto);
+    flex-direction: var(--stack-direction, row);
+    align-items: var(--stack-align-items, center);
+    align-self: var(--stack-align-self, stretch);
   }
 
-  .vstack,
-  b-vstack {
-    display: flex;
-    flex: 1 1 auto;
-    flex-direction: column;
-    align-self: stretch;
+  @include loop-breakpoints-up() using ($breakpoint, $infix) {
+    .vstack#{$infix} {
+      @include container-breakpoint-up($breakpoint) {
+        --stack-direction: column;
+        --stack-align-items: stretch;
+      }
+    }
+    .hstack#{$infix} {
+      @include container-breakpoint-up($breakpoint) {
+        --stack-direction: row;
+        --stack-align-items: flex-start;
+      }
+    }
   }
   // scss-docs-end stacks
 }
index 5ec006d3c02761c096cf78a0ebeb9f9a50483bee..db7323dedbb5cf7194d34a9a9d02c8dee5ade1c3 100644 (file)
   @return if(sass(breakpoint-min($name, $breakpoints) == null): ""; else: "-#{$name}");
 }
 
+// Iterate all breakpoints and provide the current name and infix.
+//
+//    @include loop-breakpoints-up() using ($breakpoint, $infix) {
+//      // ...
+//    }
+@mixin loop-breakpoints-up($breakpoints: $grid-breakpoints) {
+  @each $breakpoint in map.keys($breakpoints) {
+    $infix: breakpoint-infix($breakpoint, $breakpoints);
+    @content($breakpoint, $infix);
+  }
+}
+
+// Iterate all breakpoints and provide the current name, next name, and next infix.
+//
+//    @include loop-breakpoints-down() using ($breakpoint, $next, $infix) {
+//      // ...
+//    }
+@mixin loop-breakpoints-down($breakpoints: $grid-breakpoints) {
+  @each $breakpoint in map.keys($breakpoints) {
+    $next: breakpoint-next($breakpoint, $breakpoints);
+    $infix: breakpoint-infix($next, $breakpoints);
+    @content($breakpoint, $next, $infix);
+  }
+}
+
+// Backwards-compatible alias for next/down breakpoint loops.
+@mixin loop-breakpoints($breakpoints: $grid-breakpoints) {
+  @include loop-breakpoints-down($breakpoints) using ($breakpoint, $next, $infix) {
+    @content($breakpoint, $next, $infix);
+  }
+}
+
 // Media of at least the minimum breakpoint width. No query for the smallest breakpoint.
 // Makes the @content apply to the given breakpoint and wider.
 @mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {
     }
   }
 }
+
+
+// Container queries
+//
+// Container queries allow elements to respond to the size of a containing element
+// rather than the viewport. These mixins mirror the media-breakpoint-* mixins above.
+//
+// scss-docs-start container-query-mixins
+
+// Set an element as a query container.
+//
+//    @include set-container();                    // container-type: inline-size
+//    @include set-container(size);                // container-type: size
+//    @include set-container(inline-size, sidebar); // container: sidebar / inline-size
+//
+@mixin set-container($type: inline-size, $name: null) {
+  @if $name {
+    container: #{$name} / #{$type};
+  } @else {
+    container-type: #{$type};
+  }
+}
+
+// Container query of at least the minimum breakpoint width. No query for the smallest breakpoint.
+// Makes the @content apply to the given breakpoint and wider within the container.
+//
+//    @include container-breakpoint-up(md) { ... }
+//    @include container-breakpoint-up(lg, sidebar) { ... }  // Query named container
+//
+@mixin container-breakpoint-up($name, $container-name: null, $breakpoints: $grid-breakpoints) {
+  $min: breakpoint-min($name, $breakpoints);
+  @if $min {
+    @if $container-name {
+      @container #{$container-name} (width >= #{$min}) {
+        @content;
+      }
+    } @else {
+      @container (width >= #{$min}) {
+        @content;
+      }
+    }
+  } @else {
+    @content;
+  }
+}
+
+// Container query of at most the maximum breakpoint width. No query for the largest breakpoint.
+// Makes the @content apply to the given breakpoint and narrower within the container.
+//
+//    @include container-breakpoint-down(lg) { ... }
+//    @include container-breakpoint-down(lg, sidebar) { ... }  // Query named container
+//
+@mixin container-breakpoint-down($name, $container-name: null, $breakpoints: $grid-breakpoints) {
+  $max: breakpoint-max($name, $breakpoints);
+  @if $max {
+    @if $container-name {
+      @container #{$container-name} (width < #{$max}) {
+        @content;
+      }
+    } @else {
+      @container (width < #{$max}) {
+        @content;
+      }
+    }
+  } @else {
+    @content;
+  }
+}
+
+// Container query that spans multiple breakpoint widths.
+// Makes the @content apply between the min and max breakpoints within the container.
+//
+//    @include container-breakpoint-between(md, xl) { ... }
+//    @include container-breakpoint-between(md, xl, sidebar) { ... }  // Query named container
+//
+@mixin container-breakpoint-between($lower, $upper, $container-name: null, $breakpoints: $grid-breakpoints) {
+  $min: breakpoint-min($lower, $breakpoints);
+  $max: breakpoint-max($upper, $breakpoints);
+
+  @if $min != null and $max != null {
+    @if $container-name {
+      @container #{$container-name} (width >= #{$min}) and (width < #{$max}) {
+        @content;
+      }
+    } @else {
+      @container (width >= #{$min}) and (width < #{$max}) {
+        @content;
+      }
+    }
+  } @else if $max == null {
+    @include container-breakpoint-up($lower, $container-name, $breakpoints) {
+      @content;
+    }
+  } @else if $min == null {
+    @include container-breakpoint-down($upper, $container-name, $breakpoints) {
+      @content;
+    }
+  }
+}
+
+// Container query between the breakpoint's minimum and maximum widths.
+// No minimum for the smallest breakpoint, and no maximum for the largest one.
+// Makes the @content apply only to the given breakpoint within the container.
+//
+//    @include container-breakpoint-only(md) { ... }
+//    @include container-breakpoint-only(md, sidebar) { ... }  // Query named container
+//
+@mixin container-breakpoint-only($name, $container-name: null, $breakpoints: $grid-breakpoints) {
+  $min:  breakpoint-min($name, $breakpoints);
+  $next: breakpoint-next($name, $breakpoints);
+  $max:  breakpoint-max($next, $breakpoints);
+
+  @if $min != null and $max != null {
+    @if $container-name {
+      @container #{$container-name} (width >= #{$min}) and (width < #{$max}) {
+        @content;
+      }
+    } @else {
+      @container (width >= #{$min}) and (width < #{$max}) {
+        @content;
+      }
+    }
+  } @else if $max == null {
+    @include container-breakpoint-up($name, $container-name, $breakpoints) {
+      @content;
+    }
+  } @else if $min == null {
+    @include container-breakpoint-down($next, $container-name, $breakpoints) {
+      @content;
+    }
+  }
+}
+// scss-docs-end container-query-mixins
index 04f5bb2b18de103a10a98c7692af6eb9e2178a4d..ebd2d61575b98f4552a7875e24721601fa726886 100644 (file)
@@ -1,11 +1,14 @@
 // Shared between modals and offcanvases
-@mixin overlay-backdrop($zindex, $backdrop-bg, $backdrop-opacity) {
+@mixin overlay-backdrop($zindex, $backdrop-bg, $backdrop-opacity, $backdrop-blur) {
   position: fixed;
   inset: 0;
   z-index: $zindex;
-  background-color: $backdrop-bg;
+  background-color: color-mix(in oklch, var(--offcanvas-backdrop-bg) var(--offcanvas-backdrop-opacity), transparent);
+  @if $backdrop-blur {
+    backdrop-filter: blur($backdrop-blur);
+  }
 
   // Fade for backdrop
   &.fade { opacity: 0; }
-  &.show { opacity: $backdrop-opacity; }
+  &.show { opacity: 1; }
 }
index 6d9baa77ccd9a1fbacc5ec7ef81f4d02eaf63e50..8183e807654caa091f1a96beae806549782b1ccb 100644 (file)
   icon: menu-button-wide-fill
   icon_color: cyan
   pages:
-    - title: Accordion
-    - title: Alert
-    - title: Avatar
-    - title: Badge
-    - title: Breadcrumb
-    - title: Buttons
-    - title: Button group
-    - title: Card
-    - title: Carousel
-    - title: Close button
-    - title: Collapse
-    - title: Dialog
-    - title: Dropdown
-    - title: List group
-    - title: Navbar
-    - title: Navs & tabs
-    - title: Offcanvas
-    - title: Pagination
-    - title: Placeholder
-    - title: Popover
-    - title: Progress
-    - title: Scrollspy
-    - title: Spinner
-    - title: Stepper
-    - title: Toasts
-    - title: Toggler
-    - title: Tooltip
+    - group: Feedback
+      pages:
+        - title: Alert
+        - title: Badge
+        - title: Placeholder
+        - title: Progress
+        - title: Scrollspy
+        - title: Spinner
+        - title: Toasts
+    - group: Overlays
+      pages:
+        - title: Dialog
+        - title: Offcanvas
+        - title: Popover
+        - title: Tooltip
+    - group: Buttons
+      pages:
+        - title: Buttons
+        - title: Button group
+        - title: Close button
+    - group: Navigation
+      pages:
+        - title: Breadcrumb
+        - title: Dropdown
+        - title: Navs & tabs
+        - title: Nav overflow
+        - title: Navbar
+        - title: Pagination
+        - title: Stepper
+    - group: Layout
+      pages:
+        - title: Card
+        - title: List group
+    - group: Interactions
+      pages:
+        - title: Carousel
+        - title: Collapse
+        - title: Toggler
 
 - title: Helpers
   icon: magic
index a5e224a00b80318281c57d2cb0b6f75ac8d1cf59..e71da5a4e6f09939db741b6d4e2adb8a664fc74a 100644 (file)
@@ -32,7 +32,7 @@ export const direction = 'rtl'
         <strong>الألبوم</strong>
       </a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarHeader" aria-controls="navbarHeader" aria-expanded="false" aria-label="تبديل التنقل">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
     </div>
   </div>
index 204062c52a9723d1f243e7c2566fe83fc764c1d1..6fe3d5fe6843b1d7988b0d077c02a07ebe91fbb0 100644 (file)
@@ -31,7 +31,7 @@ import Placeholder from "@shortcodes/Placeholder.astro"
         <strong>Album</strong>
       </a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarHeader" aria-controls="navbarHeader" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
     </div>
   </div>
index 6a981e49423c9fcf226fbe2ca0d837aff206ce42..e6086a301e4642fc0ab9c30a101ba59eab488f91 100644 (file)
@@ -10,7 +10,7 @@ import Placeholder from "@shortcodes/Placeholder.astro"
     <div class="container-fluid">
       <a class="navbar-brand" href="#">شرائح العرض</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="تبديل التنقل">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
       <div class="collapse navbar-collapse" id="navbarCollapse">
         <ul class="navbar-nav me-auto mb-2 mb-md-0">
index 2ca4339be0288903b133e273109312bcde999222..8660d7458d4c18d7f13b95961da040d915ca5174 100644 (file)
@@ -9,7 +9,7 @@ import Placeholder from "@shortcodes/Placeholder.astro"
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Carousel</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
       <div class="collapse navbar-collapse" id="navbarCollapse">
         <ul class="navbar-nav me-auto mb-2 mb-md-0">
index 2c0909adfd5eac555447bed0dc1835e7adf0c57b..bb80e8d6be0ec8ac27021b933c2df01a87909cca 100644 (file)
@@ -1186,7 +1186,7 @@ import Placeholder from "@shortcodes/Placeholder.astro"
               <img src="${getVersionedDocsPath('/assets/brand/bootstrap-logo-white.svg')}" width="38" height="30" class="d-inline-block align-top" alt="Bootstrap" loading="lazy" style="filter: invert(1) grayscale(100%) brightness(200%);">
             </a>
             <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="تبديل التنقل">
-              <span class="navbar-toggler-icon"></span>
+              <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
             </button>
             <div class="collapse navbar-collapse" id="navbarSupportedContent">
               <ul class="navbar-nav me-auto mb-2 mb-lg-0">
@@ -1225,7 +1225,7 @@ import Placeholder from "@shortcodes/Placeholder.astro"
               <img src="${getVersionedDocsPath('/assets/brand/bootstrap-logo-white.svg')}" width="38" height="30" class="d-inline-block align-top" alt="Bootstrap" loading="lazy">
             </a>
             <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent2" aria-controls="navbarSupportedContent2" aria-expanded="false" aria-label="تبديل التنقل">
-              <span class="navbar-toggler-icon"></span>
+              <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
             </button>
             <div class="collapse navbar-collapse" id="navbarSupportedContent2">
               <ul class="navbar-nav me-auto mb-2 mb-lg-0">
index 01d0af1b8ee4f4810f5c020f3952d9caab5b94c5..0b091ba283d8c8fd213e7408a6cc916744f86ff7 100644 (file)
@@ -1182,7 +1182,7 @@ export const body_class = 'bg-body-tertiary'
                 <img src="${getVersionedDocsPath('/assets/brand/bootstrap-logo-white.svg')}" width="38" height="30" class="d-inline-block align-top" alt="Bootstrap" loading="lazy" style="filter: invert(1) grayscale(100%) brightness(200%);">
               </a>
               <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
-                <span class="navbar-toggler-icon"></span>
+                <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
               </button>
               <div class="collapse navbar-collapse" id="navbarSupportedContent">
                 <ul class="navbar-nav me-auto mb-2 mb-lg-0">
@@ -1221,7 +1221,7 @@ export const body_class = 'bg-body-tertiary'
                 <img src="${getVersionedDocsPath('/assets/brand/bootstrap-logo-white.svg')}" width="38" height="30" class="d-inline-block align-top" alt="Bootstrap" loading="lazy">
               </a>
               <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent2" aria-controls="navbarSupportedContent2" aria-expanded="false" aria-label="Toggle navigation">
-                <span class="navbar-toggler-icon"></span>
+                <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
               </button>
               <div class="collapse navbar-collapse" id="navbarSupportedContent2">
                 <ul class="navbar-nav me-auto mb-2 mb-lg-0">
index 35aa348c69b25e68670fb28d1b255f647331c46a..c3889724f051bf36de12e1437c2e9a428d5c8f02 100644 (file)
@@ -15,7 +15,7 @@ export const title = 'Bottom navbar example'
   <div class="container-fluid">
     <a class="navbar-brand" href="#">Bottom navbar</a>
     <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
-      <span class="navbar-toggler-icon"></span>
+      <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
     </button>
     <div class="collapse navbar-collapse" id="navbarCollapse">
       <ul class="navbar-nav">
index 3524255c2f5fc929effb64c5defdd5f6e382c498..a2d3c704ade4d68b28c08f5a2f84778f0ad7dbba 100644 (file)
@@ -9,7 +9,7 @@ export const extra_css = ['navbar-fixed.css']
   <div class="container-fluid">
     <a class="navbar-brand" href="#">Fixed navbar</a>
     <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
-      <span class="navbar-toggler-icon"></span>
+      <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
     </button>
     <div class="collapse navbar-collapse" id="navbarCollapse">
       <ul class="navbar-nav me-auto mb-2 mb-md-0">
index 600b313ec5c1e0e882b9386ff593125c353d55bd..fd606a659d18cd63068cdc4ba1bd14e8364da7ac 100644 (file)
@@ -9,7 +9,7 @@ export const extra_css = ['navbar-static.css']
   <div class="container-fluid">
     <a class="navbar-brand" href="#">Top navbar</a>
     <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
-      <span class="navbar-toggler-icon"></span>
+      <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
     </button>
     <div class="collapse navbar-collapse" id="navbarCollapse">
       <ul class="navbar-nav me-auto mb-2 mb-md-0">
index ec6b03f76d9284d7e8b749240681c1ad43fa60b4..327616db08151d7eda2dcec108b82ab8b2c9a286 100644 (file)
@@ -10,7 +10,7 @@ export const extra_css = ['navbars-offcanvas.css']
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Dark offcanvas navbar</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNavbarDark" aria-controls="offcanvasNavbarDark" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
       <div class="offcanvas offcanvas-end text-bg-dark" tabindex="-1" id="offcanvasNavbarDark" aria-labelledby="offcanvasNavbarDarkLabel">
         <div class="offcanvas-header">
@@ -52,7 +52,7 @@ export const extra_css = ['navbars-offcanvas.css']
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Light offcanvas navbar</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNavbarLight" aria-controls="offcanvasNavbarLight" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
       <div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNavbarLight" aria-labelledby="offcanvasNavbarLightLabel">
         <div class="offcanvas-header">
@@ -94,7 +94,7 @@ export const extra_css = ['navbars-offcanvas.css']
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Responsive offcanvas navbar</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNavbar2" aria-controls="offcanvasNavbar2" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
       <div class="offcanvas offcanvas-end text-bg-dark" tabindex="-1" id="offcanvasNavbar2" aria-labelledby="offcanvasNavbar2Label">
         <div class="offcanvas-header">
index 43c73f87ca3f40f7c42602fbb12fead5983cf355..904775c3d8d07e8be0d187c997a1f0b976586cd8 100644 (file)
@@ -10,7 +10,7 @@ export const extra_css = ['navbars.css']
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Never expand</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample01" aria-controls="navbarsExample01" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
 
       <div class="collapse navbar-collapse" id="navbarsExample01">
@@ -44,7 +44,7 @@ export const extra_css = ['navbars.css']
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Always expand</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample02" aria-controls="navbarsExample02" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
 
       <div class="collapse navbar-collapse" id="navbarsExample02">
@@ -67,7 +67,7 @@ export const extra_css = ['navbars.css']
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Expand at sm</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample03" aria-controls="navbarsExample03" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
 
       <div class="collapse navbar-collapse" id="navbarsExample03">
@@ -101,7 +101,7 @@ export const extra_css = ['navbars.css']
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Expand at md</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample04" aria-controls="navbarsExample04" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
 
       <div class="collapse navbar-collapse" id="navbarsExample04">
@@ -135,7 +135,7 @@ export const extra_css = ['navbars.css']
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Expand at lg</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample05" aria-controls="navbarsExample05" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
 
       <div class="collapse navbar-collapse" id="navbarsExample05">
@@ -169,7 +169,7 @@ export const extra_css = ['navbars.css']
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Expand at xl</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample06" aria-controls="navbarsExample06" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
 
       <div class="collapse navbar-collapse" id="navbarsExample06">
@@ -203,7 +203,7 @@ export const extra_css = ['navbars.css']
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Expand at 2xl</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample2xl" aria-controls="navbarsExample2xl" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
 
       <div class="collapse navbar-collapse" id="navbarsExample2xl">
@@ -237,7 +237,7 @@ export const extra_css = ['navbars.css']
     <div class="container">
       <a class="navbar-brand" href="#">Container</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample07" aria-controls="navbarsExample07" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
 
       <div class="collapse navbar-collapse" id="navbarsExample07">
@@ -271,7 +271,7 @@ export const extra_css = ['navbars.css']
     <div class="container-xl">
       <a class="navbar-brand" href="#">Container XL</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample07XL" aria-controls="navbarsExample07XL" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
 
       <div class="collapse navbar-collapse" id="navbarsExample07XL">
@@ -308,7 +308,7 @@ export const extra_css = ['navbars.css']
   <nav class="navbar navbar-expand-lg navbar-dark bg-dark" aria-label="Tenth navbar example">
     <div class="container-fluid">
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample08" aria-controls="navbarsExample08" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
 
       <div class="collapse navbar-collapse justify-content-md-center" id="navbarsExample08">
@@ -340,7 +340,7 @@ export const extra_css = ['navbars.css']
       <div class="container-fluid">
         <a class="navbar-brand" href="#">Navbar</a>
         <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample09" aria-controls="navbarsExample09" aria-expanded="false" aria-label="Toggle navigation">
-          <span class="navbar-toggler-icon"></span>
+          <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
         </button>
 
         <div class="collapse navbar-collapse" id="navbarsExample09">
@@ -373,7 +373,7 @@ export const extra_css = ['navbars.css']
     <nav class="navbar navbar-expand-lg bg-body-tertiary rounded" aria-label="Twelfth navbar example">
       <div class="container-fluid">
         <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample10" aria-controls="navbarsExample10" aria-expanded="false" aria-label="Toggle navigation">
-          <span class="navbar-toggler-icon"></span>
+          <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
         </button>
 
         <div class="collapse navbar-collapse justify-content-md-center" id="navbarsExample10">
@@ -403,7 +403,7 @@ export const extra_css = ['navbars.css']
     <nav class="navbar navbar-expand-lg bg-body-tertiary rounded" aria-label="Thirteenth navbar example">
       <div class="container-fluid">
         <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample11" aria-controls="navbarsExample11" aria-expanded="false" aria-label="Toggle navigation">
-          <span class="navbar-toggler-icon"></span>
+          <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
         </button>
 
         <div class="collapse navbar-collapse d-lg-flex" id="navbarsExample11">
index ac94ca882af69587c2099dd155c74c5fd32bdfad..7721b551b331e6cfa06cf65c2b2c38d3f96f5845 100644 (file)
@@ -13,7 +13,7 @@ import Placeholder from "@shortcodes/Placeholder.astro"
   <div class="container-fluid">
     <a class="navbar-brand" href="#">Offcanvas navbar</a>
     <button class="navbar-toggler p-0 border-0" type="button" id="navbarSideCollapse" aria-label="Toggle navigation">
-      <span class="navbar-toggler-icon"></span>
+      <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
     </button>
 
     <div class="navbar-collapse offcanvas-collapse" id="navbarsExampleDefault">
index 8fbf4a0c33bb5dde2776ef2d278a80031ba602f0..c21d04db61fed301ce4945100d1e8dd21d2a802a 100644 (file)
@@ -23,7 +23,7 @@ export const extra_css = ['product.css']
       Aperture
     </a>
     <button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvas" aria-controls="offcanvas" aria-label="Toggle navigation">
-      <span class="navbar-toggler-icon"></span>
+      <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
     </button>
     <div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvas" aria-labelledby="offcanvasLabel">
       <div class="offcanvas-header">
index 9b9b5ebb98939e4abce38296cff88bd162c5d8c0..a36cf8511d1b7a4823fa70bfd2a8e1e20b5ee154 100644 (file)
@@ -13,7 +13,7 @@ export const body_class = 'd-flex flex-column h-100'
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Fixed navbar</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
       <div class="collapse navbar-collapse" id="navbarCollapse">
         <ul class="navbar-nav me-auto mb-2 mb-md-0">
index aec219bca5ce6ebf6167890a3dfb631500a8bce9..c6498fc4812eb9d13bcf15e46a9706bde10c9e76 100644 (file)
@@ -11,6 +11,7 @@ import OpenCollectiveIcon from '@components/icons/OpenCollectiveIcon.astro'
 import XIcon from '@components/icons/XIcon.astro'
 import Versions from '@components/header/Versions.astro'
 import ThemeToggler from '@layouts/partials/ThemeToggler.astro'
+import CloseButton from '@components/shortcodes/CloseButton.astro'
 
 interface Props {
   addedIn?: CollectionEntry<'docs'>['data']['added']
@@ -23,21 +24,20 @@ const { addedIn, layout, title } = Astro.props
 
 <header class="navbar navbar-expand-lg bg-body bd-navbar border-bottom sticky-top bd-sticky-navbar">
   <nav class="container-2xl bd-gutter flex-wrap flex-lg-nowrap" aria-label="Main navigation">
+    {/* Docs sidebar toggle - only shown on docs pages */}
     {
       layout === 'docs' && (
-        <div class="bd-navbar-toggle">
-          <button
-            class="navbar-toggler p-2"
-            type="button"
-            data-bs-toggle="offcanvas"
-            data-bs-target="#bdSidebar"
-            aria-controls="bdSidebar"
-            aria-label="Toggle docs navigation"
-          >
-            <HamburgerIcon class="bi" height={24} width={24} />
-            <span class="d-none fs-6 pe-1">Browse</span>
-          </button>
-        </div>
+        <button
+          class="btn btn-icon navbar-btn-icon d-lg-none ms--2"
+          type="button"
+          data-bs-toggle="offcanvas"
+          data-bs-target="#bdSidebar"
+          aria-controls="bdSidebar"
+          aria-label="Toggle docs navigation"
+        >
+          <HamburgerIcon class="navbar-toggler-icon" height={20} width={20} />
+          <span class="d-none fs-6 pe-1">Browse</span>
+        </button>
       )
     }
     {layout !== 'docs' && <div class="d-lg-none" style="width: 4.25rem;" />}
@@ -46,11 +46,12 @@ const { addedIn, layout, title } = Astro.props
       <BootstrapWhiteFillIcon class="d-block my-1" height={32} width={40} />
     </a>
 
+    {/* Search and main nav toggle */}
     <div class="d-flex">
       <div class="bd-search" id="docsearch" data-bd-docs-version={getConfig().docs_version}></div>
 
       <button
-        class="navbar-toggler d-flex d-lg-none order-3 p-2"
+        class="btn btn-icon navbar-btn-icon d-lg-none order-3 me--2"
         type="button"
         data-bs-toggle="offcanvas"
         data-bs-target="#bdNavbar"
@@ -61,6 +62,7 @@ const { addedIn, layout, title } = Astro.props
       </button>
     </div>
 
+    {/* Main navigation - offcanvas on mobile, inline on desktop */}
     <div
       class="offcanvas-lg offcanvas-end flex-grow-1"
       tabindex="-1"
@@ -69,17 +71,12 @@ const { addedIn, layout, title } = Astro.props
     >
       <div class="offcanvas-header px-4 pb-0">
         <h5 class="offcanvas-title text-white" id="bdNavbarOffcanvasLabel">Bootstrap</h5>
-        <button
-          type="button"
-          class="btn-close btn-close-white"
-          data-bs-dismiss="offcanvas"
-          aria-label="Close"
-          data-bs-target="#bdNavbar"></button>
+        <CloseButton dismiss="offcanvas" target="#bdNavbar" />
       </div>
 
       <div class="offcanvas-body p-4 pt-0 p-lg-0">
         <hr class="d-lg-none text-white-50" />
-        <ul class="navbar-nav flex-row flex-wrap bd-navbar-nav">
+        <ul class="nav navbar-nav flex-row flex-wrap bd-navbar-nav">
           <LinkItem active={layout === 'docs'} href={getVersionedDocsPath('getting-started/install/')} track>
             Docs
           </LinkItem>
@@ -90,7 +87,7 @@ const { addedIn, layout, title } = Astro.props
 
         <hr class="d-lg-none text-white-50" />
 
-        <ul class="navbar-nav flex-row flex-wrap ms-md-auto">
+        <ul class="nav navbar-nav flex-row flex-wrap ms-md-auto">
           <LinkItem class="nav-link py-2 px-0 px-lg-2" href={getConfig().github_org} target="_blank" rel="noopener">
             <GitHubIcon class="navbar-nav-svg" height={16} width={16} />
             <small class="d-lg-none ms-2">GitHub</small>
@@ -108,6 +105,7 @@ const { addedIn, layout, title } = Astro.props
             <OpenCollectiveIcon class="navbar-nav-svg" height={16} width={16} />
             <small class="d-lg-none ms-2">Open Collective</small>
           </LinkItem>
+
           <li class="nav-item py-2 py-lg-1 col-12 col-lg-auto">
             <div class="vr d-none d-lg-flex mx-lg-2 my-auto"></div>
             <hr class="d-lg-none my-2" />
index 8ff4730a854645b7be3ddd9a215fa7ce09629eeb..2e17d759c747adc8d90bda7e9d0cad47430959a5 100644 (file)
@@ -14,10 +14,9 @@ const { class: className, height, width } = Astro.props
   height={height}
   width={width}
   aria-hidden="true"
+  stroke="currentColor"
+  stroke-width="1"
+  stroke-linecap="round"
 >
-  <path
-    fill-rule="evenodd"
-    d="M2.5 11.5A.5.5 0 0 1 3 11h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4A.5.5 0 0 1 3 3h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"
-  >
-  </path>
+  <path d="M1 3.5h14M1 8h14M1 12.5h14" />
 </svg>
index 16e3fe61fa022164690006251061c14bd4e01147..de337053e8fc0b9aeb7e31166c4e439b01d2e4ac 100644 (file)
@@ -9,12 +9,17 @@ interface Props {
    * Sets the `data-bs-dismiss` attribute.
    */
   dismiss?: string
+  /**
+   * The target to dismiss (e.g., "#my-offcanvas").
+   * Sets the `data-bs-target` attribute.
+   */
+  target?: string
 }
 
-const { class: className, dismiss } = Astro.props
+const { class: className, dismiss, target } = Astro.props
 ---
 
-<button type="button" class:list={["btn-close", className]} data-bs-dismiss={dismiss} aria-label="Close">
+<button type="button" class:list={["btn-close", className]} data-bs-dismiss={dismiss} aria-label="Close" data-bs-target={target}>
   <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="20" height="20" fill="none">
     <path fill="currentcolor" d="M12 0a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm-.646 4.646a.5.5 0 0 0-.707 0L8 7.293 5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.647a.5.5 0 1 0 .708.707L8 8.707l2.647 2.646a.5.5 0 1 0 .707-.707L8.707 8l2.646-2.646a.5.5 0 0 0 0-.708z"/>
   </svg>
index fa5f99d2b12a7d5a2f6038db6bc641d957571aba..a376bf8d8d26ab1e8bb429a7baa801b1db976a77 100644 (file)
@@ -340,7 +340,7 @@ if (highlightedCode) {
 {nestedInExample ? (
   <>
     {!noToolbar && (
-      <div class="hstack highlight-toolbar">
+      <div class="hstack highlight-toolbar align-items-center">
         {highlightedTabs ? (
           <div class="code-tabs">
             {highlightedTabs.map((tab, index) => (
@@ -388,7 +388,7 @@ if (highlightedCode) {
 ) : (
   <div class="bd-code-snippet">
     {!noToolbar && (
-      <div class="hstack highlight-toolbar">
+      <div class="hstack highlight-toolbar align-items-center">
         {highlightedTabs ? (
           <div class="code-tabs">
             {highlightedTabs.map((tab, index) => (
diff --git a/site/src/components/shortcodes/NavbarPlacementPlayground.astro b/site/src/components/shortcodes/NavbarPlacementPlayground.astro
new file mode 100644 (file)
index 0000000..3f633e6
--- /dev/null
@@ -0,0 +1,142 @@
+---
+import Example from '@components/shortcodes/Example.astro'
+
+const placements = [
+  { value: '', label: 'Default (static)' },
+  { value: 'fixed-top', label: 'Fixed top' },
+  { value: 'fixed-bottom', label: 'Fixed bottom' },
+  { value: 'sticky-top', label: 'Sticky top' },
+  { value: 'sticky-bottom', label: 'Sticky bottom' }
+]
+---
+
+<div class="bg-1 p-3 fs-sm rounded-3">
+  <div class="vstack gap-1">
+    <label class="form-label fw-semibold mb-0">Placement</label>
+    <div class="dropdown">
+      <button
+        type="button"
+        class="btn btn-outline theme-secondary dropdown-toggle"
+        id="navbar-placement-dropdown"
+        data-bs-toggle="dropdown"
+        aria-expanded="false"
+        data-placement=""
+      >
+        <span>Default (static)</span>
+        <svg class="bi ms-1" width="16" height="16" aria-hidden="true">
+          <use href="#chevron-expand" />
+        </svg>
+      </button>
+      <ul class="dropdown-menu" aria-labelledby="navbar-placement-dropdown">
+        {placements.map((p) => (
+          <li>
+            <a
+              class:list={['dropdown-item', { 'active': p.value === '' }]}
+              href="#"
+              data-placement={p.value}
+            >
+              {p.label}
+            </a>
+          </li>
+        ))}
+      </ul>
+    </div>
+  </div>
+</div>
+
+<Example
+  code={`<nav class="navbar bg-1">
+  <div class="container-fluid">
+    <a class="navbar-brand" href="#">Default</a>
+  </div>
+</nav>`}
+  id="navbar-placement-preview"
+/>
+
+<script>
+  const placementDropdownButton = document.querySelector('#navbar-placement-dropdown') as HTMLButtonElement
+  const placementDropdownItems = document.querySelectorAll('#navbar-placement-dropdown + .dropdown-menu .dropdown-item')
+  const previewContainer = document.querySelector('#navbar-placement-preview') as HTMLElement
+  const previewNavbar = previewContainer?.querySelector('.navbar') as HTMLElement
+  const codeSnippet = previewContainer?.closest('.bd-example-snippet')?.querySelector('.highlight code') as HTMLElement
+
+  const placementClasses = ['fixed-top', 'fixed-bottom', 'sticky-top', 'sticky-bottom']
+
+  const placementLabels: Record<string, string> = {
+    '': 'Default',
+    'fixed-top': 'Fixed top',
+    'fixed-bottom': 'Fixed bottom',
+    'sticky-top': 'Sticky top',
+    'sticky-bottom': 'Sticky bottom'
+  }
+
+  function updatePlacement(placement: string) {
+    if (!previewNavbar || !placementDropdownButton) return
+
+    // Update dropdown button text
+    const labelSpan = placementDropdownButton.querySelector('span')
+    if (labelSpan) {
+      labelSpan.textContent = placementLabels[placement] ? `${placementLabels[placement]}${placement ? '' : ' (static)'}` : 'Default (static)'
+    }
+    placementDropdownButton.dataset.placement = placement
+
+    // Update active state in dropdown
+    placementDropdownItems.forEach(item => {
+      const itemPlacement = (item as HTMLElement).dataset.placement
+      item.classList.toggle('active', itemPlacement === placement)
+    })
+
+    // Remove all placement classes
+    placementClasses.forEach(cls => {
+      previewNavbar.classList.remove(cls)
+    })
+
+    // Add new placement class if not default
+    if (placement) {
+      previewNavbar.classList.add(placement)
+    }
+
+    // Update the brand text to show current placement
+    const brand = previewNavbar.querySelector('.navbar-brand')
+    if (brand) {
+      brand.textContent = placementLabels[placement] || 'Default'
+    }
+
+    // Update code snippet
+    updateCodeSnippet(placement)
+  }
+
+  function updateCodeSnippet(placement: string) {
+    if (!codeSnippet) return
+
+    const placementClass = placement ? ` ${placement}` : ''
+    const label = placementLabels[placement] || 'Default'
+
+    const htmlCode = `<nav class="navbar${placementClass} bg-body-tertiary">
+  <div class="container-fluid">
+    <a class="navbar-brand" href="#">${label}</a>
+  </div>
+</nav>`
+
+    codeSnippet.className = 'language-html'
+    codeSnippet.textContent = htmlCode
+
+    if (typeof window !== 'undefined' && (window as any).Prism) {
+      (window as any).Prism.highlightElement(codeSnippet)
+    }
+  }
+
+  // Initialize dropdown
+  if (placementDropdownButton) {
+    const placementDropdown = bootstrap.Dropdown.getOrCreateInstance(placementDropdownButton)
+
+    placementDropdownItems.forEach(item => {
+      item.addEventListener('click', (e) => {
+        e.preventDefault()
+        const placement = (item as HTMLElement).dataset.placement || ''
+        updatePlacement(placement)
+        placementDropdown.hide()
+      })
+    })
+  }
+</script>
diff --git a/site/src/components/shortcodes/ResizableExample.astro b/site/src/components/shortcodes/ResizableExample.astro
new file mode 100644 (file)
index 0000000..9fed55d
--- /dev/null
@@ -0,0 +1,69 @@
+---
+import { replacePlaceholdersInHtml } from '@libs/placeholder'
+import Code from '@components/shortcodes/Code.astro'
+
+interface Props {
+  /**
+   * The example code.
+   */
+  code: string | string[]
+  /**
+   * The CSS class(es) to be added to the resizable container.
+   */
+  class?: string
+  /**
+   * Alias for class prop.
+   */
+  className?: string
+  /**
+   * Initial width of the resizable container.
+   * @default '100%'
+   */
+  initialWidth?: string
+  /**
+   * Minimum width of the resizable container.
+   * @default '200px'
+   */
+  minWidth?: string
+  /**
+   * Whether to show the code markup below.
+   * @default true
+   */
+  showMarkup?: boolean
+}
+
+const {
+  code,
+  class: classFromClass,
+  className,
+  initialWidth = '100%',
+  minWidth = '200px',
+  showMarkup = true
+} = Astro.props
+
+// Support both class and className props
+const containerClass = className || classFromClass
+
+let markup = Array.isArray(code) ? code.join('\n') : code
+markup = replacePlaceholdersInHtml(markup)
+
+const simplifiedMarkup = markup
+  .replace(
+    /<svg.*class="bd-placeholder-img(?:-lg)?(?: *?bd-placeholder-img-lg)? ?(.*?)".*?<\/svg>/g,
+    (match, classes) => `<img src="..."${classes ? ` class="${classes}"` : ''} alt="...">`
+  )
+---
+
+<div class="bd-example-snippet bd-code-snippet">
+  <div class="bd-example bd-example-resizable p-2">
+    <div
+      class:list={['bd-resizable-container', containerClass]}
+      style={`width: ${initialWidth}; min-width: ${minWidth};`}
+    >
+      <Fragment set:html={markup} />
+    </div>
+  </div>
+  {showMarkup && (
+    <Code code={simplifiedMarkup} lang="html" nestedInExample={true} />
+  )}
+</div>
index f4fec221f84acab4c5809a0572a5ee4de2e09fa5..edfe19f75d55155bb076139de03122f3b7e8a1c2 100644 (file)
@@ -20,7 +20,7 @@ Alerts are available for any length of text, as well as an optional close button
 
 Use any of our variant theme classes for color mode adaptive, contextual styling.
 
-<Example code={getData('theme-colors').map((themeColor) => `<div class="alert theme-${themeColor.name}" role="alert">
+<Example class="vstack gap-3" code={getData('theme-colors').map((themeColor) => `<div class="alert theme-${themeColor.name}" role="alert">
     A simple ${themeColor.name} alert—check it out!
   </div>`)} />
 
@@ -41,7 +41,7 @@ We use the following JavaScript to trigger our live alert demo:
 
 Use the `.alert-link` utility class to quickly provide matching colored links within any alert.
 
-<Example code={getData('theme-colors').map((themeColor) => `<div class="alert theme-${themeColor.name}" role="alert">
+<Example class="vstack gap-3" code={getData('theme-colors').map((themeColor) => `<div class="alert theme-${themeColor.name}" role="alert">
     A simple ${themeColor.name} alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
   </div>`)} />
 
index 4bc89801e0c62450eec0f1498ef9008bd6cec35d..c1625593f2b6b24bcb4d10cdefd18ed6e7637bdc 100644 (file)
@@ -70,6 +70,28 @@ The markup for a dialog is straightforward:
 </button>
 ```
 
+## Dark dialog
+
+To make a dialog dark, add `data-bs-theme="dark"` to the `<dialog>` element.
+
+<dialog class="dialog" id="exampleDialogDark" data-bs-theme="dark">
+  <div class="dialog-header">
+    <h1 class="dialog-title">Dark dialog title</h1>
+    <CloseButton dismiss="dialog" />
+  </div>
+  <div class="dialog-body">
+    <p>This is a native dialog element, only it’s set to dark mode. It uses the browser's built-in modal behavior for accessibility and focus management.</p>
+  </div>
+  <div class="dialog-footer">
+    <button type="button" class="btn btn-solid theme-secondary" data-bs-dismiss="dialog">Close</button>
+    <button type="button" class="btn btn-solid theme-primary">Save changes</button>
+  </div>
+</dialog>
+
+<Example showMarkup={false} code={`<button type="button" class="btn btn-solid theme-primary" data-bs-toggle="dialog" data-bs-target="#exampleDialogDark">
+  Open dark dialog
+</button>`} />
+
 ## Static backdrop
 
 When `backdrop` is set to `static`, the dialog will not close when clicking outside of it. Click the button below to try it.
index d90616851b107e1d15175a32ecca93b542fd8ba4..787bf27b603ba82fb8dbf9a327133e8e5eeebdf3 100644 (file)
@@ -105,7 +105,7 @@ And putting it to use in a navbar:
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Navbar</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDarkDropdown" aria-controls="navbarNavDarkDropdown" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
       <div class="collapse navbar-collapse" id="navbarNavDarkDropdown">
         <ul class="navbar-nav">
@@ -570,11 +570,13 @@ The dropdown plugin requires the following JavaScript files if you're building B
 | --- | --- | --- | --- |
 | `autoClose` | boolean, string | `true` | Configure the auto close behavior of the dropdown: <ul class="my-2"><li>`true` - the dropdown will be closed by clicking outside or inside the dropdown menu.</li><li>`false` - the dropdown will be closed by clicking the toggle button and manually calling `hide` or `toggle` method. (Also will not be closed by pressing <kbd>Esc</kbd> key)</li><li>`'inside'` - the dropdown will be closed (only) by clicking inside the dropdown menu.</li> <li>`'outside'` - the dropdown will be closed (only) by clicking outside the dropdown menu.</li></ul> Note: the dropdown can always be closed with the <kbd>Esc</kbd> key. |
 | `boundary` | string, element | `'clippingParents'` | Overflow constraint boundary of the dropdown menu (applies only to the shift middleware). By default it's `clippingParents` and can accept an HTMLElement reference (via JavaScript only). For more information refer to Floating UI's [shift docs](https://floating-ui.com/docs/shift). |
+| `container` | string, element, boolean | `false` | Appends the dropdown menu to a specific element when shown. Use `'body'` or `true` to append to the document body, which helps escape containers with `overflow: hidden`. The menu is moved back to its original position when hidden. |
 | `display` | string | `'dynamic'` | By default, we use Floating UI for dynamic positioning. Disable this with `static`. |
 | `offset` | array, string, function | `[0, 2]` | Offset of the dropdown relative to its target. You can pass a string in data attributes with comma separated values like: `data-bs-offset="10,20"`. When a function is used to determine the offset, it is called with an object containing the placement, the reference, and floating rects as its first argument. The triggering element DOM node is passed as the second argument. The function must return an array with two numbers: [skidding, distance]. For more information refer to Floating UI's [offset docs](https://floating-ui.com/docs/offset). |
 | `floatingConfig` | null, object, function | `null` | To change Bootstrap's default Floating UI config, see [Floating UI's configuration](https://floating-ui.com/docs/computePosition). When a function is used to create the Floating UI configuration, it's called with an object that contains the Bootstrap's default Floating UI configuration. It helps you use and merge the default with your own configuration. The function must return a configuration object for Floating UI. |
 | `placement` | string | `'bottom-start'` | Placement of the dropdown menu. Physical placements: `'top'`, `'bottom'`, `'left'`, `'right'`. Logical placements (RTL-aware): `'start'`, `'end'`. All support alignment modifiers: `-start`, `-end`. Supports responsive prefixes like `'bottom-start md:end'`. |
 | `reference` | string, element, object | `'toggle'` | Reference element of the dropdown menu. Accepts the values of `'toggle'`, `'parent'`, an HTMLElement reference or an object providing `getBoundingClientRect`. For more information refer to Floating UI's [virtual elements docs](https://floating-ui.com/docs/virtual-elements). |
+| `strategy` | string | `'absolute'` | Positioning strategy for the dropdown. Use `'absolute'` for default positioning, or `'fixed'` to escape containers with `overflow: hidden`. For more information refer to Floating UI's [strategy docs](https://floating-ui.com/docs/computePosition#strategy). |
 | `submenuTrigger` | string | `'both'` | How submenus are triggered. Use `'click'` for click only, `'hover'` for hover only, or `'both'` for both click and hover activation. |
 | `submenuDelay` | number | `100` | Delay in milliseconds before closing a submenu when the mouse leaves. Provides a grace period for diagonal mouse movement toward the submenu. |
 </BsTable>
diff --git a/site/src/content/docs/components/nav-overflow.mdx b/site/src/content/docs/components/nav-overflow.mdx
new file mode 100644 (file)
index 0000000..542e27e
--- /dev/null
@@ -0,0 +1,297 @@
+---
+title: Nav overflow
+description: Automatically collapse navigation items into a "More" dropdown when space is limited using the Priority+ pattern.
+toc: true
+---
+
+## How it works
+
+The nav overflow component (also known as the "Priority+" pattern) automatically detects when navigation items don't fit within their container and moves them into a dropdown menu. This provides a responsive navigation experience without requiring different markup for different screen sizes.
+
+Here's what you need to know before getting started:
+
+- Add `data-bs-toggle="nav-overflow"` to any `.nav` element to enable automatic overflow detection.
+- **Responds to container size**, not viewport size. The component uses a ResizeObserver to monitor its own width, so it works perfectly in embedded contexts, documentation examples, and responsive containers.
+- Overflow items are cloned into a "More" dropdown menu while the originals are hidden.
+- Works with all nav styles: default, pills, tabs, and underline.
+- Active and disabled states are preserved in the overflow menu.
+
+<Callout name="info-prefersreducedmotion" />
+
+## Examples
+
+Add `data-bs-toggle="nav-overflow"` to your nav element. When items don't fit, they'll automatically move to a "More" dropdown. Drag the right edge of the container below to see how nav items automatically move to the "More" dropdown as space becomes limited.
+
+<ResizableExample code={`<ul class="nav nav-pills" data-bs-toggle="nav-overflow">
+    <li class="nav-item">
+      <a class="nav-link active" aria-current="page" href="#">Home</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Dashboard</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Products</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Services</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Analytics</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Reports</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Settings</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Help</a>
+    </li>
+  </ul>`} />
+
+### With tabs
+
+The overflow pattern works seamlessly with tabbed navigation:
+
+<ResizableExample code={`<ul class="nav nav-tabs" data-bs-toggle="nav-overflow">
+    <li class="nav-item">
+      <a class="nav-link active" aria-current="page" href="#">Overview</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Details</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">History</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Activity</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Comments</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Attachments</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Related</a>
+    </li>
+  </ul>`} />
+
+### With underline
+
+<ResizableExample code={`<ul class="nav nav-underline" data-bs-toggle="nav-overflow">
+    <li class="nav-item">
+      <a class="nav-link active" aria-current="page" href="#">Home</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Features</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Pricing</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">FAQs</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">About</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Contact</a>
+    </li>
+  </ul>`} />
+
+### Keep items visible
+
+Use the `.nav-overflow-keep` class on items that should never be moved to the overflow menu. These items will remain visible regardless of available space—useful for high-priority items like "Home" or action buttons.
+
+<ResizableExample code={`<ul class="nav nav-pills" data-bs-toggle="nav-overflow">
+    <li class="nav-item nav-overflow-keep">
+      <a class="nav-link active" aria-current="page" href="#">Home</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Products</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Services</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">About</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Blog</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Careers</a>
+    </li>
+    <li class="nav-item nav-overflow-keep">
+      <a class="nav-link" href="#">Contact</a>
+    </li>
+  </ul>`} />
+
+### With disabled items
+
+Disabled states are preserved when items move to the overflow menu:
+
+<ResizableExample code={`<ul class="nav nav-pills" data-bs-toggle="nav-overflow">
+    <li class="nav-item">
+      <a class="nav-link active" aria-current="page" href="#">Active</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Link</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Another link</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link disabled" aria-disabled="true">Disabled</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">More content</a>
+    </li>
+    <li class="nav-item">
+      <a class="nav-link" href="#">Even more</a>
+    </li>
+  </ul>`} />
+
+### In a navbar
+
+The nav overflow pattern can also be used within a [navbar]([[docsref:/components/navbar]]) for horizontal navigation that adapts to available space:
+
+<ResizableExample code={`<nav class="navbar navbar-expand bg-body-tertiary">
+    <div class="container-fluid">
+      <a class="navbar-brand" href="#">Brand</a>
+      <ul class="nav navbar-nav" data-bs-toggle="nav-overflow">
+        <li class="nav-item">
+          <a class="nav-link active" aria-current="page" href="#">Home</a>
+        </li>
+        <li class="nav-item">
+          <a class="nav-link" href="#">Features</a>
+        </li>
+        <li class="nav-item">
+          <a class="nav-link" href="#">Pricing</a>
+        </li>
+        <li class="nav-item">
+          <a class="nav-link" href="#">About</a>
+        </li>
+        <li class="nav-item">
+          <a class="nav-link" href="#">Contact</a>
+        </li>
+      </ul>
+    </div>
+  </nav>`} />
+
+## Customizing the toggle
+
+### Custom text
+
+Use the `moreText` option to customize the text shown in the overflow toggle button:
+
+```js
+const nav = document.querySelector('.nav')
+new bootstrap.NavOverflow(nav, {
+  moreText: 'See all'
+})
+```
+
+### Custom icon
+
+Provide a custom icon via the `moreIcon` option:
+
+```js
+const nav = document.querySelector('.nav')
+new bootstrap.NavOverflow(nav, {
+  moreIcon: '<i class="bi bi-three-dots"></i>',
+  moreText: '' // Hide text, show only icon
+})
+```
+
+### Minimum visible items
+
+Use the `threshold` option to ensure a minimum number of items remain visible before the overflow kicks in:
+
+```js
+const nav = document.querySelector('.nav')
+new bootstrap.NavOverflow(nav, {
+  threshold: 3 // Always keep at least 3 items visible
+})
+```
+
+## Usage
+
+### Via data attributes
+
+Add `data-bs-toggle="nav-overflow"` to any `.nav` element to automatically enable the overflow behavior.
+
+```html
+<ul class="nav nav-pills" data-bs-toggle="nav-overflow">
+  <li class="nav-item"><a class="nav-link" href="#">Link 1</a></li>
+  <li class="nav-item"><a class="nav-link" href="#">Link 2</a></li>
+  <li class="nav-item"><a class="nav-link" href="#">Link 3</a></li>
+  <!-- More items... -->
+</ul>
+```
+
+### Via JavaScript
+
+Initialize the nav overflow component manually:
+
+```js
+const navElement = document.querySelector('.nav')
+const navOverflow = new bootstrap.NavOverflow(navElement, {
+  moreText: 'More',
+  threshold: 2
+})
+```
+
+### Options
+
+<JsDataAttributes />
+
+<BsTable>
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `moreText` | string | `'More'` | Text label for the overflow toggle button. |
+| `moreIcon` | string | `'<svg>...</svg>'` | SVG or HTML icon for the overflow toggle button. |
+| `threshold` | number | `0` | Minimum number of items to keep visible before showing overflow. |
+</BsTable>
+
+### Methods
+
+<Callout name="danger-async-methods" type="danger" />
+
+You can create a nav overflow instance with the constructor:
+
+```js
+const navOverflow = new bootstrap.NavOverflow('#myNav', {
+  threshold: 2
+})
+```
+
+<BsTable>
+| Method | Description |
+| --- | --- |
+| `dispose` | Destroys the nav overflow instance and restores items to their original positions. |
+| `getInstance` | *Static* method to get the nav overflow instance associated with a DOM element. |
+| `getOrCreateInstance` | *Static* method to get the nav overflow instance or create a new one if not initialized. |
+| `update` | Manually recalculates which items should overflow. Called automatically on resize. |
+</BsTable>
+
+### Events
+
+Bootstrap's nav overflow component exposes events for hooking into overflow functionality.
+
+<BsTable>
+| Event type | Description |
+| --- | --- |
+| `update.bs.navoverflow` | Fired when the overflow calculation is updated (on resize or manual update). |
+| `overflow.bs.navoverflow` | Fired when items are moved to the overflow menu. Event includes `overflowCount` and `visibleCount` properties. |
+</BsTable>
+
+```js
+const myNav = document.getElementById('myNav')
+
+myNav.addEventListener('overflow.bs.navoverflow', event => {
+  console.log(`${event.overflowCount} items moved to overflow`)
+  console.log(`${event.visibleCount} items still visible`)
+})
+```
index c3778fb161298464e6f3c2bde175af4bf87a178b..8769cf356cf47e86e567306b6fac842eb5459f77 100644 (file)
@@ -1,19 +1,70 @@
 ---
 title: Navbar
-description: Documentation and examples for Bootstrap’s powerful, responsive navigation header, the navbar. Includes support for branding, navigation, and more, including support for our collapse plugin.
+description: Create flexible, responsive navigation bars. Includes support for branding, navigation, and a built-in offcanvas for mobile.
 toc: true
 ---
 
 import { getConfig } from '@libs/config'
 
+## Example
+
+Here's a navbar that includes most supported sub-components and a responsive right drawer with [our Offcanvas plugin]([[docsref:/components/offcanvas]]). Use the lower-right corner grip to resize to preview responsive behavior.
+
+<ResizableExample code={`<nav class="navbar navbar-expand-md bg-1">
+    <div class="container-fluid">
+      <a class="navbar-brand" href="#">Navbar</a>
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarOffcanvas" aria-controls="navbarOffcanvas" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round">
+          <path d="M1 3.5h14M1 8h14M1 12.5h14"/>
+        </svg>
+      </button>
+      <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarOffcanvas" aria-labelledby="navbarOffcanvasLabel">
+        <div class="offcanvas-header">
+          <h5 class="offcanvas-title" id="navbarOffcanvasLabel">Menu</h5>
+          <CloseButton dismiss="offcanvas" />
+        </div>
+        <div class="offcanvas-body mb-2 mb-md-0">
+          <ul class="nav navbar-nav me-auto">
+            <li class="nav-item">
+              <a class="nav-link active" aria-current="page" href="#">Home</a>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link" href="#">Link</a>
+            </li>
+            <li class="nav-item dropdown">
+              <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" data-bs-container="body" aria-expanded="false">
+                Dropdown
+              </a>
+              <ul class="dropdown-menu">
+                <li><a class="dropdown-item" href="#">Action</a></li>
+                <li><a class="dropdown-item" href="#">Another action</a></li>
+                <li><hr class="dropdown-divider"></li>
+                <li><a class="dropdown-item" href="#">Something else here</a></li>
+              </ul>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link disabled" aria-disabled="true">Disabled</a>
+            </li>
+          </ul>
+          <form class="hstack gap-2" role="search">
+            <input class="form-control" type="search" placeholder="Search" aria-label="Search"/>
+            <button class="btn-solid btn-icon theme-secondary" type="submit">
+              <svg class="bi" width="16" height="16"><use href="#search" /></svg>
+            </button>
+          </form>
+        </div>
+      </div>
+    </div>
+  </nav>`} />
+
 ## How it works
 
-Heres what you need to know before getting started with the navbar:
+Here's what you need to know before getting started with the navbar:
 
-- Navbars require a wrapping `.navbar` with `.navbar-expand{-sm|-md|-lg|-xl|-2xl}` for responsive collapsing and [color scheme](#color-schemes) classes.
+- Navbars require a wrapping `.navbar` with `.navbar-expand-{breakpoint}` for responsive collapsing and [color scheme](#color-schemes) classes.
 - Navbars and their contents are fluid by default. Change the [container](#containers) to limit their horizontal width in different ways.
 - Use our [margin]([[docsref:/utilities/margin]]), [padding]([[docsref:/utilities/padding]]), and [flex]([[docsref:/utilities/flex]]) utility classes for controlling spacing and alignment within navbars.
-- Navbars are responsive by default, but you can easily modify them to change that. Responsive behavior depends on our Collapse JavaScript plugin.
+- Navbars are responsive by default using our **offcanvas component**. On mobile, navigation links slide in from the side as a drawer.
 - Ensure accessibility by using a `<nav>` element or, if using a more generic element such as a `<div>`, add a `role="navigation"` to every navbar to explicitly identify it as a landmark region for users of assistive technologies.
 - Indicate the current item by using `aria-current="page"` for the current page or `aria-current="true"` for the current item in a set.
 
@@ -25,52 +76,9 @@ Navbars come with built-in support for a handful of sub-components. Choose from
 
 - `.navbar-brand` for your company, product, or project name.
 - `.navbar-nav` for a full-height and lightweight navigation (including support for dropdowns).
-- `.navbar-toggler` for use with our collapse plugin and other [navigation toggling](#responsive-behaviors) behaviors.
+- `.navbar-toggler` for use with our offcanvas plugin and other [navigation toggling](#responsive-behaviors) behaviors.
 - Flex and spacing utilities for any form controls and actions.
 - `.navbar-text` for adding vertically centered strings of text.
-- `.collapse.navbar-collapse` for grouping and hiding navbar contents by a parent breakpoint.
-- Add an optional `.navbar-nav-scroll` to set a `max-height` and [scroll expanded navbar content](#scrolling).
-
-Here’s an example of all the sub-components included in a responsive light-themed navbar that automatically collapses at the `lg` (large) breakpoint.
-
-<Example code={`<nav class="navbar navbar-expand-lg">
-    <div class="container-fluid">
-      <a class="navbar-brand" href="#">Navbar</a>
-      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
-      </button>
-      <div class="collapse navbar-collapse" id="navbarSupportedContent">
-        <ul class="nav navbar-nav me-auto mb-2 mb-lg-0">
-          <li class="nav-item">
-            <a class="nav-link active" aria-current="page" href="#">Home</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link" href="#">Link</a>
-          </li>
-          <li class="nav-item dropdown">
-            <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
-              Dropdown
-            </a>
-            <ul class="dropdown-menu">
-              <li><a class="dropdown-item" href="#">Action</a></li>
-              <li><a class="dropdown-item" href="#">Another action</a></li>
-              <li><hr class="dropdown-divider"></li>
-              <li><a class="dropdown-item" href="#">Something else here</a></li>
-            </ul>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link disabled" aria-disabled="true">Disabled</a>
-          </li>
-        </ul>
-        <form class="d-flex" role="search">
-          <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
-          <button class="btn btn-subtle theme-primary" type="submit">Search</button>
-        </form>
-      </div>
-    </div>
-  </nav>`} />
-
-This example uses [background]([[docsref:/utilities/background]]) (`bg-body-tertiary`) and [margin]([[docsref:/utilities/margin]]) (`me-auto`, `mb-2`, `mb-lg-0`, `me-2`) utility classes.
 
 ### Brand
 
@@ -81,14 +89,14 @@ The `.navbar-brand` can be applied to most elements, but an anchor works best, a
 Add your text within an element with the `.navbar-brand` class.
 
 <Example code={`<!-- As a link -->
-  <nav class="navbar bg-body-tertiary">
+  <nav class="navbar bg-1 fg-2">
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Navbar</a>
     </div>
   </nav>
 
   <!-- As a heading -->
-  <nav class="navbar bg-body-tertiary">
+  <nav class="navbar bg-1 fg-2">
     <div class="container-fluid">
       <span class="navbar-brand mb-0 h1">Navbar</span>
     </div>
@@ -98,7 +106,7 @@ Add your text within an element with the `.navbar-brand` class.
 
 You can replace the text within the `.navbar-brand` with an `<img>`.
 
-<Example code={`<nav class="navbar bg-body-tertiary">
+<Example code={`<nav class="navbar bg-1 fg-2">
     <div class="container">
       <a class="navbar-brand" href="#">
         <img src="/docs/${getConfig().docs_version}/assets/brand/bootstrap-logo.svg" alt="Bootstrap" width="30" height="24">
@@ -110,7 +118,7 @@ You can replace the text within the `.navbar-brand` with an `<img>`.
 
 You can also make use of some additional utilities to add an image and text at the same time. Note the addition of `.d-inline-block` and `.align-text-top` on the `<img>`.
 
-<Example code={`<nav class="navbar bg-body-tertiary">
+<Example code={`<nav class="navbar bg-1 fg-2">
     <div class="container-fluid">
       <a class="navbar-brand" href="#">
         <img src="/docs/${getConfig().docs_version}/assets/brand/bootstrap-logo.svg" alt="Logo" width="30" height="24" class="d-inline-block align-text-top">
@@ -127,45 +135,57 @@ Add the `.active` class on `.nav-link` to indicate the current page.
 
 Please note that you should also add the `aria-current` attribute on the active `.nav-link`.
 
-<Example code={`<nav class="navbar navbar-expand-lg bg-body-tertiary">
+<ResizableExample code={`<nav class="navbar navbar-expand-sm bg-1 fg-2">
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Navbar</a>
-      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
-      <div class="collapse navbar-collapse" id="navbarNav">
-        <ul class="navbar-nav">
-          <li class="nav-item">
-            <a class="nav-link active" aria-current="page" href="#">Home</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link" href="#">Features</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link" href="#">Pricing</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link disabled" aria-disabled="true">Disabled</a>
-          </li>
-        </ul>
+      <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarNav" aria-labelledby="navbarNavLabel">
+        <div class="offcanvas-header">
+          <h5 class="offcanvas-title" id="navbarNavLabel">Menu</h5>
+          <CloseButton dismiss="offcanvas" />
+        </div>
+        <div class="offcanvas-body">
+          <ul class="navbar-nav">
+            <li class="nav-item">
+              <a class="nav-link active" aria-current="page" href="#">Home</a>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link" href="#">Features</a>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link" href="#">Pricing</a>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link disabled" aria-disabled="true">Disabled</a>
+            </li>
+          </ul>
+        </div>
       </div>
     </div>
   </nav>`} />
 
 And because we use classes for our navs, you can avoid the list-based approach entirely if you like.
 
-<Example code={`<nav class="navbar navbar-expand-lg bg-body-tertiary">
+<ResizableExample code={`<nav class="navbar navbar-expand-sm bg-1 fg-2">
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Navbar</a>
-      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
-      <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
-        <div class="navbar-nav">
-          <a class="nav-link active" aria-current="page" href="#">Home</a>
-          <a class="nav-link" href="#">Features</a>
-          <a class="nav-link" href="#">Pricing</a>
-          <a class="nav-link disabled" aria-disabled="true">Disabled</a>
+      <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarNavAltMarkup" aria-labelledby="navbarNavAltMarkupLabel">
+        <div class="offcanvas-header">
+          <h5 class="offcanvas-title" id="navbarNavAltMarkupLabel">Menu</h5>
+          <CloseButton dismiss="offcanvas" />
+        </div>
+        <div class="offcanvas-body">
+          <div class="nav nav-pills navbar-nav">
+            <a class="nav-link active" aria-current="page" href="#">Home</a>
+            <a class="nav-link" href="#">Features</a>
+            <a class="nav-link" href="#">Pricing</a>
+            <a class="nav-link disabled" aria-disabled="true">Disabled</a>
+          </div>
         </div>
       </div>
     </div>
@@ -173,34 +193,40 @@ And because we use classes for our navs, you can avoid the list-based approach e
 
 You can also use dropdowns in your navbar. Dropdown menus require a wrapping element for positioning, so be sure to use separate and nested elements for `.nav-item` and `.nav-link` as shown below.
 
-<Example code={`<nav class="navbar navbar-expand-lg bg-body-tertiary">
+<ResizableExample code={`<nav class="navbar navbar-expand-lg bg-1 fg-2">
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Navbar</a>
-      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
-      <div class="collapse navbar-collapse" id="navbarNavDropdown">
-        <ul class="navbar-nav">
-          <li class="nav-item">
-            <a class="nav-link active" aria-current="page" href="#">Home</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link" href="#">Features</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link" href="#">Pricing</a>
-          </li>
-          <li class="nav-item dropdown">
-            <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
-              Dropdown link
-            </a>
-            <ul class="dropdown-menu">
-              <li><a class="dropdown-item" href="#">Action</a></li>
-              <li><a class="dropdown-item" href="#">Another action</a></li>
-              <li><a class="dropdown-item" href="#">Something else here</a></li>
-            </ul>
-          </li>
-        </ul>
+      <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarNavDropdown" aria-labelledby="navbarNavDropdownLabel">
+        <div class="offcanvas-header">
+          <h5 class="offcanvas-title" id="navbarNavDropdownLabel">Menu</h5>
+          <CloseButton dismiss="offcanvas" />
+        </div>
+        <div class="offcanvas-body">
+          <ul class="navbar-nav">
+            <li class="nav-item">
+              <a class="nav-link active" aria-current="page" href="#">Home</a>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link" href="#">Features</a>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link" href="#">Pricing</a>
+            </li>
+            <li class="nav-item dropdown">
+              <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" data-bs-container="body" aria-expanded="false">
+                Dropdown link
+              </a>
+              <ul class="dropdown-menu">
+                <li><a class="dropdown-item" href="#">Action</a></li>
+                <li><a class="dropdown-item" href="#">Another action</a></li>
+                <li><a class="dropdown-item" href="#">Something else here</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
       </div>
     </div>
   </nav>`} />
@@ -209,7 +235,7 @@ You can also use dropdowns in your navbar. Dropdown menus require a wrapping ele
 
 Place various form controls and components within a navbar:
 
-<Example code={`<nav class="navbar bg-body-tertiary">
+<Example code={`<nav class="navbar bg-1 fg-2">
     <div class="container-fluid">
       <form class="d-flex" role="search">
         <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
@@ -220,7 +246,7 @@ Place various form controls and components within a navbar:
 
 Immediate child elements of `.navbar` use flex layout and will default to `justify-content: space-between`. Use additional [flex utilities]([[docsref:/utilities/flex]]) as needed to adjust this behavior.
 
-<Example code={`<nav class="navbar bg-body-tertiary">
+<Example code={`<nav class="navbar bg-1 fg-2">
     <div class="container-fluid">
       <a class="navbar-brand">Navbar</a>
       <form class="d-flex" role="search">
@@ -232,7 +258,7 @@ Immediate child elements of `.navbar` use flex layout and will default to `justi
 
 Input groups work, too. If your navbar is an entire form, or mostly a form, you can use the `<form>` element as the container and save some HTML.
 
-<Example code={`<nav class="navbar bg-body-tertiary">
+<Example code={`<nav class="navbar bg-1 fg-2">
     <form class="container-fluid">
       <div class="input-group">
         <span class="input-group-text" id="basic-addon1">@</span>
@@ -243,7 +269,7 @@ Input groups work, too. If your navbar is an entire form, or mostly a form, you
 
 Various buttons are supported as part of these navbar forms, too. This is also a great reminder that vertical alignment utilities can be used to align different sized elements.
 
-<Example code={`<nav class="navbar bg-body-tertiary">
+<Example code={`<nav class="navbar bg-1 fg-2">
     <form class="container-fluid justify-content-start">
       <button class="btn btn-outline-success me-2" type="button">Main button</button>
       <button class="btn btn-sm btn-outline-secondary" type="button">Smaller button</button>
@@ -254,7 +280,7 @@ Various buttons are supported as part of these navbar forms, too. This is also a
 
 Navbars may contain bits of text with the help of `.navbar-text`. This class adjusts vertical alignment and horizontal spacing for strings of text.
 
-<Example code={`<nav class="navbar bg-body-tertiary">
+<Example code={`<nav class="navbar bg-1 fg-2">
     <div class="container-fluid">
       <span class="navbar-text">
         Navbar text with an inline element
@@ -264,14 +290,106 @@ Navbars may contain bits of text with the help of `.navbar-text`. This class adj
 
 Mix and match with other components and utilities as needed.
 
-<Example code={`<nav class="navbar navbar-expand-lg bg-body-tertiary">
+<ResizableExample code={`<nav class="navbar navbar-expand-md bg-1 fg-2">
     <div class="container-fluid">
       <a class="navbar-brand" href="#">Navbar w/ text</a>
-      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
-      <div class="collapse navbar-collapse" id="navbarText">
-        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
+      <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarText" aria-labelledby="navbarTextLabel">
+        <div class="offcanvas-header">
+          <h5 class="offcanvas-title" id="navbarTextLabel">Menu</h5>
+          <CloseButton dismiss="offcanvas" />
+        </div>
+        <div class="offcanvas-body">
+          <ul class="navbar-nav me-auto mb-2 mb-md-0">
+            <li class="nav-item">
+              <a class="nav-link active" aria-current="page" href="#">Home</a>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link" href="#">Features</a>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link" href="#">Pricing</a>
+            </li>
+          </ul>
+          <span class="navbar-text">
+            Navbar text with an inline element
+          </span>
+        </div>
+      </div>
+    </div>
+  </nav>`} />
+
+## Dark navbar
+
+To make a navbar dark, add `data-bs-theme="dark"` to the `.navbar` element.
+
+<ResizableExample code={`<nav class="navbar navbar-expand-md bg-1" data-bs-theme="dark">
+    <div class="container-fluid">
+      <a class="navbar-brand" href="#">Dark navbar</a>
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarDarkOffcanvas" aria-controls="navbarDarkOffcanvas" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round">
+          <path d="M1 3.5h14M1 8h14M1 12.5h14"/>
+        </svg>
+      </button>
+      <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarDarkOffcanvas" aria-labelledby="navbarDarkOffcanvasLabel">
+        <div class="offcanvas-header">
+          <h5 class="offcanvas-title" id="navbarDarkOffcanvasLabel">Menu</h5>
+          <CloseButton dismiss="offcanvas" />
+        </div>
+        <div class="offcanvas-body mb-2 mb-md-0">
+          <ul class="nav navbar-nav me-auto">
+            <li class="nav-item">
+              <a class="nav-link active" aria-current="page" href="#">Home</a>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link" href="#">Link</a>
+            </li>
+            <li class="nav-item dropdown">
+              <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" data-bs-container="body" aria-expanded="false">
+                Dropdown
+              </a>
+              <ul class="dropdown-menu">
+                <li><a class="dropdown-item" href="#">Action</a></li>
+                <li><a class="dropdown-item" href="#">Another action</a></li>
+                <li><hr class="dropdown-divider"></li>
+                <li><a class="dropdown-item" href="#">Something else here</a></li>
+              </ul>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link disabled" aria-disabled="true">Disabled</a>
+            </li>
+          </ul>
+          <form class="hstack gap-2" role="search">
+            <input class="form-control" type="search" placeholder="Search" aria-label="Search"/>
+            <button class="btn-solid btn-icon theme-secondary" type="submit">
+              <svg class="bi" width="16" height="16"><use href="#search" /></svg>
+            </button>
+          </form>
+        </div>
+      </div>
+    </div>
+  </nav>`} />
+
+## Color schemes
+
+Navbar themes are easier than ever thanks to Bootstrap's combination of Sass and CSS variables. The default is our "light navbar" for use with light background colors, but you can also apply `data-bs-theme="dark"` to the `.navbar` parent for dark background colors. Then, customize with `.bg-*` and additional utilities.
+
+<ResizableExample className="d-flex flex-column gap-2" showMarkup={false} code={`
+<nav class="navbar navbar-expand-md" data-bs-theme="dark">
+  <div class="container-fluid">
+    <a class="navbar-brand" href="#">Navbar</a>
+    <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
+      <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
+    </button>
+    <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarColor01" aria-labelledby="navbarColor01Label">
+      <div class="offcanvas-header">
+        <h5 class="offcanvas-title" id="navbarColor01Label">Menu</h5>
+        <CloseButton dismiss="offcanvas" />
+      </div>
+      <div class="offcanvas-body">
+        <ul class="navbar-nav me-auto mb-2 mb-md-0">
           <li class="nav-item">
             <a class="nav-link active" aria-current="page" href="#">Home</a>
           </li>
@@ -281,109 +399,92 @@ Mix and match with other components and utilities as needed.
           <li class="nav-item">
             <a class="nav-link" href="#">Pricing</a>
           </li>
+          <li class="nav-item">
+            <a class="nav-link" href="#">About</a>
+          </li>
         </ul>
-        <span class="navbar-text">
-          Navbar text with an inline element
-        </span>
+        <form class="d-flex" role="search">
+          <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
+          <button class="btn btn-outline theme-secondary" type="submit">Search</button>
+        </form>
       </div>
     </div>
-  </nav>`} />
-
-## Color schemes
-
-Navbar themes are easier than ever thanks to Bootstrap’s combination of Sass and CSS variables. The default is our “light navbar” for use with light background colors, but you can also apply `data-bs-theme="dark"` to the `.navbar` parent for dark background colors. Then, customize with `.bg-*` and additional utilities.
-
-<Example showMarkup={false} code={`
-<nav class="navbar navbar-expand-lg bg-dark border-bottom border-body" data-bs-theme="dark">
-  <div class="container-fluid">
-    <a class="navbar-brand" href="#">Navbar</a>
-    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
-      <span class="navbar-toggler-icon"></span>
-    </button>
-    <div class="collapse navbar-collapse" id="navbarColor01">
-      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
-        <li class="nav-item">
-          <a class="nav-link active" aria-current="page" href="#">Home</a>
-        </li>
-        <li class="nav-item">
-          <a class="nav-link" href="#">Features</a>
-        </li>
-        <li class="nav-item">
-          <a class="nav-link" href="#">Pricing</a>
-        </li>
-        <li class="nav-item">
-          <a class="nav-link" href="#">About</a>
-        </li>
-      </ul>
-      <form class="d-flex" role="search">
-        <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
-        <button class="btn btn-outline-light" type="submit">Search</button>
-      </form>
-    </div>
   </div>
 </nav>
 
-<nav class="navbar navbar-expand-lg bg-primary" data-bs-theme="dark">
+<nav class="navbar navbar-expand-md bg-primary" data-bs-theme="dark">
   <div class="container-fluid">
     <a class="navbar-brand" href="#">Navbar</a>
-    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor02" aria-controls="navbarColor02" aria-expanded="false" aria-label="Toggle navigation">
-      <span class="navbar-toggler-icon"></span>
+    <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarColor02" aria-controls="navbarColor02" aria-expanded="false" aria-label="Toggle navigation">
+      <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
     </button>
-    <div class="collapse navbar-collapse" id="navbarColor02">
-      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
-        <li class="nav-item">
-          <a class="nav-link active" aria-current="page" href="#">Home</a>
-        </li>
-        <li class="nav-item">
-          <a class="nav-link" href="#">Features</a>
-        </li>
-        <li class="nav-item">
-          <a class="nav-link" href="#">Pricing</a>
-        </li>
-        <li class="nav-item">
-          <a class="nav-link" href="#">About</a>
-        </li>
-      </ul>
-      <form class="d-flex" role="search">
-        <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
-        <button class="btn btn-outline-light" type="submit">Search</button>
-      </form>
+    <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarColor02" aria-labelledby="navbarColor02Label">
+      <div class="offcanvas-header">
+        <h5 class="offcanvas-title" id="navbarColor02Label">Menu</h5>
+        <CloseButton dismiss="offcanvas" />
+      </div>
+      <div class="offcanvas-body">
+        <ul class="navbar-nav me-auto mb-2 mb-md-0">
+          <li class="nav-item">
+            <a class="nav-link active" aria-current="page" href="#">Home</a>
+          </li>
+          <li class="nav-item">
+            <a class="nav-link" href="#">Features</a>
+          </li>
+          <li class="nav-item">
+            <a class="nav-link" href="#">Pricing</a>
+          </li>
+          <li class="nav-item">
+            <a class="nav-link" href="#">About</a>
+          </li>
+        </ul>
+        <form class="d-flex" role="search">
+          <input class="form-control me-2" data-bs-theme="light" type="search" placeholder="Search" aria-label="Search"/>
+          <button class="btn btn-solid theme-inverse" type="submit">Search</button>
+        </form>
+      </div>
     </div>
   </div>
 </nav>
 
-<nav class="navbar navbar-expand-lg" style="background-color: #e3f2fd;" data-bs-theme="light">
+<nav class="navbar navbar-expand-md bg-1" data-bs-theme="light">
   <div class="container-fluid">
     <a class="navbar-brand" href="#">Navbar</a>
-    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor03" aria-controls="navbarColor03" aria-expanded="false" aria-label="Toggle navigation">
-      <span class="navbar-toggler-icon"></span>
+    <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarColor03" aria-controls="navbarColor03" aria-expanded="false" aria-label="Toggle navigation">
+      <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
     </button>
-    <div class="collapse navbar-collapse" id="navbarColor03">
-      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
-        <li class="nav-item">
-          <a class="nav-link active" aria-current="page" href="#">Home</a>
-        </li>
-        <li class="nav-item">
-          <a class="nav-link" href="#">Features</a>
-        </li>
-        <li class="nav-item">
-          <a class="nav-link" href="#">Pricing</a>
-        </li>
-        <li class="nav-item">
-          <a class="nav-link" href="#">About</a>
-        </li>
-      </ul>
-      <form class="d-flex" role="search">
-        <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
-        <button class="btn btn-outline-primary" type="submit">Search</button>
-      </form>
+    <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarColor03" aria-labelledby="navbarColor03Label">
+      <div class="offcanvas-header">
+        <h5 class="offcanvas-title" id="navbarColor03Label">Menu</h5>
+        <CloseButton dismiss="offcanvas" />
+      </div>
+      <div class="offcanvas-body">
+        <ul class="navbar-nav me-auto mb-2 mb-md-0">
+          <li class="nav-item">
+            <a class="nav-link active" aria-current="page" href="#">Home</a>
+          </li>
+          <li class="nav-item">
+            <a class="nav-link" href="#">Features</a>
+          </li>
+          <li class="nav-item">
+            <a class="nav-link" href="#">Pricing</a>
+          </li>
+          <li class="nav-item">
+            <a class="nav-link" href="#">About</a>
+          </li>
+        </ul>
+        <form class="d-flex" role="search">
+          <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
+          <button class="btn btn-outline-primary" type="submit">Search</button>
+        </form>
+      </div>
     </div>
   </div>
 </nav>
 `} />
 
 ```html
-<nav class="navbar bg-dark border-bottom border-body" data-bs-theme="dark">
+<nav class="navbar bg-body border-bottom border-body" data-bs-theme="dark">
   <!-- Navbar content -->
 </nav>
 
@@ -398,10 +499,10 @@ Navbar themes are easier than ever thanks to Bootstrap’s combination of Sass a
 
 ## Containers
 
-Although its not required, you can wrap a navbar in a `.container` to center it on a page–though note that an inner container is still required. Or you can add a container inside the `.navbar` to only center the contents of a [fixed or static top navbar](#placement).
+Although it's not required, you can wrap a navbar in a `.container` to center it on a page–though note that an inner container is still required. Or you can add a container inside the `.navbar` to only center the contents of a [fixed or static top navbar](#placement).
 
 <Example code={`<div class="container">
-    <nav class="navbar navbar-expand-lg bg-body-tertiary">
+    <nav class="navbar navbar-expand-lg bg-1 fg-2">
       <div class="container-fluid">
         <a class="navbar-brand" href="#">Navbar</a>
       </div>
@@ -410,7 +511,7 @@ Although it’s not required, you can wrap a navbar in a `.container` to center
 
 Use any of the responsive containers to change how wide the content in your navbar is presented.
 
-<Example code={`<nav class="navbar navbar-expand-lg bg-body-tertiary">
+<Example code={`<nav class="navbar navbar-expand-lg bg-1 fg-2">
     <div class="container-md">
       <a class="navbar-brand" href="#">Navbar</a>
     </div>
@@ -420,237 +521,129 @@ Use any of the responsive containers to change how wide the content in your navb
 
 Use our [position utilities]([[docsref:/utilities/position]]) to place navbars in non-static positions. Choose from fixed to the top, fixed to the bottom, stickied to the top (scrolls with the page until it reaches the top, then stays there), or stickied to the bottom (scrolls with the page until it reaches the bottom, then stays there).
 
-Fixed navbars use `position: fixed`, meaning they’re pulled from the normal flow of the DOM and may require custom CSS (e.g., `padding-top` on the `<body>`) to prevent overlap with other elements.
-
-<Example code={`<nav class="navbar bg-body-tertiary">
-    <div class="container-fluid">
-      <a class="navbar-brand" href="#">Default</a>
-    </div>
-  </nav>`} />
-
-<Example code={`<nav class="navbar fixed-top bg-body-tertiary">
-    <div class="container-fluid">
-      <a class="navbar-brand" href="#">Fixed top</a>
-    </div>
-  </nav>`} />
-
-<Example code={`<nav class="navbar fixed-bottom bg-body-tertiary">
-    <div class="container-fluid">
-      <a class="navbar-brand" href="#">Fixed bottom</a>
-    </div>
-  </nav>`} />
-
-<Example code={`<nav class="navbar sticky-top bg-body-tertiary">
-    <div class="container-fluid">
-      <a class="navbar-brand" href="#">Sticky top</a>
-    </div>
-  </nav>`} />
-
-<Example code={`<nav class="navbar sticky-bottom bg-body-tertiary">
-    <div class="container-fluid">
-      <a class="navbar-brand" href="#">Sticky bottom</a>
-    </div>
-  </nav>`} />
+Fixed navbars use `position: fixed`, meaning they're pulled from the normal flow of the DOM and may require custom CSS (e.g., `padding-top` on the `<body>`) to prevent overlap with other elements.
 
-## Scrolling
+<NavbarPlacementPlayground />
 
-Add `.navbar-nav-scroll` to a `.navbar-nav` (or other navbar sub-component) to enable vertical scrolling within the toggleable contents of a collapsed navbar. By default, scrolling kicks in at `75vh` (or 75% of the viewport height), but you can override that with the local CSS custom property `--bs-navbar-height` or custom styles. At larger viewports when the navbar is expanded, content will appear as it does in a default navbar.
+## Responsive behaviors
 
-Please note that this behavior comes with a potential drawback of `overflow`—when setting `overflow-y: auto` (required to scroll the content here), `overflow-x` is the equivalent of `auto`, which will crop some horizontal content.
+Navbars can use `.navbar-toggler`, and `.navbar-expand-lg{-sm|-md|-lg|-xl|-2xl}` classes to determine when their content appears in an offcanvas drawer or inline. In combination with other utilities, you can easily choose when to show or hide particular elements.
 
-Here’s an example navbar using `.navbar-nav-scroll` with `style="--bs-scroll-height: 100px;"`, with some extra margin utilities for optimum spacing.
+For navbars that never collapse, add the `.navbar-expand-lg` class on the navbar. For navbars that always show the offcanvas drawer, don't add any `.navbar-expand-lg` class.
 
-<Example code={`<nav class="navbar navbar-expand-lg bg-body-tertiary">
-    <div class="container-fluid">
-      <a class="navbar-brand" href="#">Navbar scroll</a>
-      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarScroll" aria-controls="navbarScroll" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
-      </button>
-      <div class="collapse navbar-collapse" id="navbarScroll">
-        <ul class="navbar-nav me-auto my-2 my-lg-0 navbar-nav-scroll" style="--bs-scroll-height: 100px;">
-          <li class="nav-item">
-            <a class="nav-link active" aria-current="page" href="#">Home</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link" href="#">Link</a>
-          </li>
-          <li class="nav-item dropdown">
-            <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
-              Link
-            </a>
-            <ul class="dropdown-menu">
-              <li><a class="dropdown-item" href="#">Action</a></li>
-              <li><a class="dropdown-item" href="#">Another action</a></li>
-              <li><hr class="dropdown-divider"></li>
-              <li><a class="dropdown-item" href="#">Something else here</a></li>
-            </ul>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link disabled" aria-disabled="true">Link</a>
-          </li>
-        </ul>
-        <form class="d-flex" role="search">
-          <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
-          <button class="btn btn-outline-success" type="submit">Search</button>
-        </form>
-      </div>
-    </div>
-  </nav>`} />
-
-## Responsive behaviors
+### Offcanvas drawer
 
-Navbars can use `.navbar-toggler`, `.navbar-collapse`, and `.navbar-expand{-sm|-md|-lg|-xl|-2xl}` classes to determine when their content collapses behind a button. In combination with other utilities, you can easily choose when to show or hide particular elements.
+By default, navbars use the [offcanvas component]([[docsref:/components/offcanvas]]) for their responsive behavior. This provides a modern drawer-style menu that slides in from any side of the screen.
 
-For navbars that never collapse, add the `.navbar-expand` class on the navbar. For navbars that always collapse, don’t add any `.navbar-expand` class.
+#### Drawer placement
 
-### Toggler
+You can customize which side the drawer appears from using offcanvas placement classes:
 
-Navbar togglers are left-aligned by default, but should they follow a sibling element like a `.navbar-brand`, they’ll automatically be aligned to the far right. Reversing your markup will reverse the placement of the toggler. Below are examples of different toggle styles.
+- `.offcanvas-start` - slides in from the left (or right in RTL)
+- `.offcanvas-end` - slides in from the right (or left in RTL)
+- `.offcanvas-top` - slides in from the top
+- `.offcanvas-bottom` - slides in from the bottom
 
-With no `.navbar-brand` shown at the smallest breakpoint:
+These examples omit the `.navbar-expand-lg-*` class to always show the drawer behavior. Click the toggler to see the drawer slide in from different directions.
 
-<Example code={`<nav class="navbar navbar-expand-lg bg-body-tertiary">
+<ResizableExample code={`<nav class="navbar navbar-expand-md bg-1 fg-2">
     <div class="container-fluid">
-      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTogglerDemo01" aria-controls="navbarTogglerDemo01" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+      <a class="navbar-brand" href="#">Left Drawer</a>
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarLeftDrawer" aria-controls="navbarLeftDrawer" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
-      <div class="collapse navbar-collapse" id="navbarTogglerDemo01">
-        <a class="navbar-brand" href="#">Hidden brand</a>
-        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
-          <li class="nav-item">
-            <a class="nav-link active" aria-current="page" href="#">Home</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link" href="#">Link</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link disabled" aria-disabled="true">Disabled</a>
-          </li>
-        </ul>
-        <form class="d-flex" role="search">
-          <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
-          <button class="btn btn-outline-success" type="submit">Search</button>
-        </form>
+      <div class="offcanvas offcanvas-start" tabindex="-1" id="navbarLeftDrawer" aria-labelledby="navbarLeftDrawerLabel">
+        <div class="offcanvas-header">
+          <h5 class="offcanvas-title" id="navbarLeftDrawerLabel">Menu</h5>
+          <CloseButton dismiss="offcanvas" />
+        </div>
+        <div class="offcanvas-body">
+          <ul class="navbar-nav me-auto mb-2 mb-md-0">
+            <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Home</a></li>
+            <li class="nav-item"><a class="nav-link" href="#">Link</a></li>
+            <li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Disabled</a></li>
+          </ul>
+        </div>
       </div>
     </div>
   </nav>`} />
 
-With a brand name shown on the left and toggler on the right:
-
-<Example code={`<nav class="navbar navbar-expand-lg bg-body-tertiary">
+<ResizableExample code={`<nav class="navbar navbar-expand-md bg-1 fg-2">
     <div class="container-fluid">
-      <a class="navbar-brand" href="#">Navbar</a>
-      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+      <a class="navbar-brand" href="#">Top Drawer</a>
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarTopDrawer" aria-controls="navbarTopDrawer" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
-      <div class="collapse navbar-collapse" id="navbarTogglerDemo02">
-        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
-          <li class="nav-item">
-            <a class="nav-link active" aria-current="page" href="#">Home</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link" href="#">Link</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link disabled" aria-disabled="true">Disabled</a>
-          </li>
-        </ul>
-        <form class="d-flex" role="search">
-          <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
-          <button class="btn btn-outline-success" type="submit">Search</button>
-        </form>
+      <div class="offcanvas offcanvas-top" tabindex="-1" id="navbarTopDrawer" aria-labelledby="navbarTopDrawerLabel">
+        <div class="offcanvas-header">
+          <h5 class="offcanvas-title" id="navbarTopDrawerLabel">Menu</h5>
+          <CloseButton dismiss="offcanvas" />
+        </div>
+        <div class="offcanvas-body">
+          <ul class="navbar-nav me-auto mb-2 mb-md-0">
+            <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Home</a></li>
+            <li class="nav-item"><a class="nav-link" href="#">Link</a></li>
+            <li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Disabled</a></li>
+          </ul>
+        </div>
       </div>
     </div>
   </nav>`} />
 
-With a toggler on the left and brand name on the right:
+### Toggler
 
-<Example code={`<nav class="navbar navbar-expand-lg bg-body-tertiary">
-    <div class="container-fluid">
-      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
-      </button>
-      <a class="navbar-brand" href="#">Navbar</a>
-      <div class="collapse navbar-collapse" id="navbarTogglerDemo03">
-        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
-          <li class="nav-item">
-            <a class="nav-link active" aria-current="page" href="#">Home</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link" href="#">Link</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link disabled" aria-disabled="true">Disabled</a>
-          </li>
-        </ul>
-        <form class="d-flex" role="search">
-          <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
-          <button class="btn btn-outline-success" type="submit">Search</button>
-        </form>
-      </div>
-    </div>
-  </nav>`} />
+Navbar togglers are left-aligned by default, but should they follow a sibling element like a `.navbar-brand`, they'll automatically be aligned to the far right. Reversing your markup will reverse the placement of the toggler.
 
-### External content
+These examples omit the `.navbar-expand-lg-*` class to always show the collapsed state with the toggler visible. Click the toggler to open the offcanvas drawer.
 
-Sometimes you want to use the collapse plugin to trigger a container element for content that structurally sits outside of the `.navbar` . Because our plugin works on the `id` and `data-bs-target` matching, that’s easily done!
+With no `.navbar-brand` shown (hidden inside the drawer):
 
-<Example code={`<div class="collapse" id="navbarToggleExternalContent" data-bs-theme="dark">
-    <div class="bg-dark p-4">
-      <h5 class="text-body-emphasis h4">Collapsed content</h5>
-      <span class="text-body-secondary">Toggleable via the navbar brand.</span>
-    </div>
-  </div>
-  <nav class="navbar navbar-dark bg-dark">
+<ResizableExample code={`<nav class="navbar navbar-expand-md bg-1 fg-2">
     <div class="container-fluid">
-      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarToggleExternalContent" aria-controls="navbarToggleExternalContent" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarTogglerDemo01" aria-controls="navbarTogglerDemo01" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
+      <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarTogglerDemo01" aria-labelledby="navbarTogglerDemo01Label">
+        <div class="offcanvas-header">
+          <h5 class="offcanvas-title" id="navbarTogglerDemo01Label">Hidden brand</h5>
+          <CloseButton dismiss="offcanvas" />
+        </div>
+        <div class="offcanvas-body">
+          <a class="navbar-brand" href="#">Hidden brand</a>
+          <ul class="navbar-nav me-auto mb-2 mb-md-0">
+            <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Home</a></li>
+            <li class="nav-item"><a class="nav-link" href="#">Link</a></li>
+            <li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Disabled</a></li>
+          </ul>
+          <form class="d-flex" role="search">
+            <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
+            <button class="btn btn-outline-success" type="submit">Search</button>
+          </form>
+        </div>
+      </div>
     </div>
   </nav>`} />
 
-When you do this, we recommend including additional JavaScript to move the focus programmatically to the container when it is opened. Otherwise, keyboard users and users of assistive technologies will likely have a hard time finding the newly revealed content - particularly if the container that was opened comes *before* the toggler in the document’s structure. We also recommend making sure that the toggler has the `aria-controls` attribute, pointing to the `id` of the content container. In theory, this allows assistive technology users to jump directly from the toggler to the container it controls–but support for this is currently quite patchy.
-
-### Offcanvas
-
-Transform your expanding and collapsing navbar into an offcanvas drawer with the [offcanvas component]([[docsref:/components/offcanvas]]). We extend both the offcanvas default styles and use our `.navbar-expand-*` classes to create a dynamic and flexible navigation sidebar.
-
-In the example below, to create an offcanvas navbar that is always collapsed across all breakpoints, omit the `.navbar-expand-*` class entirely.
+With a brand name shown on the left and toggler on the right:
 
-<Example code={`<nav class="navbar bg-body-tertiary fixed-top">
+<ResizableExample code={`<nav class="navbar bg-1 fg-2">
     <div class="container-fluid">
-      <a class="navbar-brand" href="#">Offcanvas navbar</a>
-      <button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNavbar" aria-controls="offcanvasNavbar" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+      <a class="navbar-brand" href="#">Navbar</a>
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
-      <div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNavbar" aria-labelledby="offcanvasNavbarLabel">
+      <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarTogglerDemo02" aria-labelledby="navbarTogglerDemo02Label">
         <div class="offcanvas-header">
-          <h5 class="offcanvas-title" id="offcanvasNavbarLabel">Offcanvas</h5>
+          <h5 class="offcanvas-title" id="navbarTogglerDemo02Label">Menu</h5>
           <CloseButton dismiss="offcanvas" />
         </div>
         <div class="offcanvas-body">
-          <ul class="navbar-nav justify-content-end flex-grow-1 pe-3">
-            <li class="nav-item">
-              <a class="nav-link active" aria-current="page" href="#">Home</a>
-            </li>
-            <li class="nav-item">
-              <a class="nav-link" href="#">Link</a>
-            </li>
-            <li class="nav-item dropdown">
-              <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
-                Dropdown
-              </a>
-              <ul class="dropdown-menu">
-                <li><a class="dropdown-item" href="#">Action</a></li>
-                <li><a class="dropdown-item" href="#">Another action</a></li>
-                <li>
-                  <hr class="dropdown-divider">
-                </li>
-                <li><a class="dropdown-item" href="#">Something else here</a></li>
-              </ul>
-            </li>
+          <ul class="navbar-nav me-auto mb-2">
+            <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Home</a></li>
+            <li class="nav-item"><a class="nav-link" href="#">Link</a></li>
+            <li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Disabled</a></li>
           </ul>
-          <form class="d-flex mt-3" role="search">
+          <form class="d-flex" role="search">
             <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
             <button class="btn btn-outline-success" type="submit">Search</button>
           </form>
@@ -659,64 +652,57 @@ In the example below, to create an offcanvas navbar that is always collapsed acr
     </div>
   </nav>`} />
 
-To create an offcanvas navbar that expands into a normal navbar at a specific breakpoint like `lg`, use `.navbar-expand-lg`.
-
-```html
-<nav class="navbar navbar-expand-lg bg-body-tertiary fixed-top">
-  <a class="navbar-brand" href="#">Offcanvas navbar</a>
-  <button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarOffcanvasLg" aria-controls="navbarOffcanvasLg" aria-label="Toggle navigation">
-    <span class="navbar-toggler-icon"></span>
-  </button>
-  <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarOffcanvasLg" aria-labelledby="navbarOffcanvasLgLabel">
-    ...
-  </div>
-</nav>
-```
-
-When using offcanvas in a dark navbar, be aware that you may need to have a dark background on the offcanvas content to avoid the text becoming illegible. In the example below, we add `.navbar-dark` and `.bg-dark` to the `.navbar`, `.text-bg-dark` to the `.offcanvas`, `.dropdown-menu-dark` to `.dropdown-menu`, and `.btn-close-white` to `.btn-close` for proper styling with a dark offcanvas.
+With a toggler on the left and brand name on the right:
 
-<Example code={`<nav class="navbar navbar-dark bg-dark fixed-top">
+<ResizableExample code={`<nav class="navbar bg-1 fg-2">
     <div class="container-fluid">
-      <a class="navbar-brand" href="#">Offcanvas dark navbar</a>
-      <button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasDarkNavbar" aria-controls="offcanvasDarkNavbar" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
       </button>
-      <div class="offcanvas offcanvas-end text-bg-dark" tabindex="-1" id="offcanvasDarkNavbar" aria-labelledby="offcanvasDarkNavbarLabel">
+      <a class="navbar-brand" href="#">Navbar</a>
+      <div class="offcanvas offcanvas-end" tabindex="-1" id="navbarTogglerDemo03" aria-labelledby="navbarTogglerDemo03Label">
         <div class="offcanvas-header">
-          <h5 class="offcanvas-title" id="offcanvasDarkNavbarLabel">Dark offcanvas</h5>
+          <h5 class="offcanvas-title" id="navbarTogglerDemo03Label">Menu</h5>
           <CloseButton dismiss="offcanvas" />
         </div>
         <div class="offcanvas-body">
-          <ul class="navbar-nav justify-content-end flex-grow-1 pe-3">
-            <li class="nav-item">
-              <a class="nav-link active" aria-current="page" href="#">Home</a>
-            </li>
-            <li class="nav-item">
-              <a class="nav-link" href="#">Link</a>
-            </li>
-            <li class="nav-item dropdown">
-              <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
-                Dropdown
-              </a>
-              <ul class="dropdown-menu dropdown-menu-dark">
-                <li><a class="dropdown-item" href="#">Action</a></li>
-                <li><a class="dropdown-item" href="#">Another action</a></li>
-                <li>
-                  <hr class="dropdown-divider">
-                </li>
-                <li><a class="dropdown-item" href="#">Something else here</a></li>
-              </ul>
-            </li>
+          <ul class="navbar-nav me-auto mb-2">
+            <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Home</a></li>
+            <li class="nav-item"><a class="nav-link" href="#">Link</a></li>
+            <li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Disabled</a></li>
           </ul>
-          <form class="d-flex mt-3" role="search">
+          <form class="d-flex" role="search">
             <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
-            <button class="btn btn-success" type="submit">Search</button>
+            <button class="btn btn-outline-success" type="submit">Search</button>
           </form>
         </div>
       </div>
     </div>
   </nav>`} />
 
+### External content
+
+Sometimes you want to use the offcanvas plugin to trigger a container element for content that structurally sits outside of the `.navbar`. Because our plugin works on the `id` and `data-bs-target` matching, that's easily done!
+
+<ResizableExample code={`<div class="offcanvas offcanvas-top" tabindex="-1" id="navbarToggleExternalContent" data-bs-theme="dark" aria-labelledby="navbarToggleExternalContentLabel">
+    <div class="offcanvas-header">
+      <h5 class="offcanvas-title" id="navbarToggleExternalContentLabel">Collapsed content</h5>
+      <CloseButton dismiss="offcanvas" />
+    </div>
+    <div class="offcanvas-body">
+      <span class="text-body-secondary">Toggleable via the navbar toggler.</span>
+    </div>
+  </div>
+  <nav class="navbar bg-body" data-bs-theme="dark">
+    <div class="container-fluid">
+      <button class="btn btn-icon navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarToggleExternalContent" aria-controls="navbarToggleExternalContent" aria-expanded="false" aria-label="Toggle navigation">
+        <svg class="navbar-toggler-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M1 3.5h14M1 8h14M1 12.5h14"/></svg>
+      </button>
+    </div>
+  </nav>`} />
+
+When you do this, we recommend including additional JavaScript to move the focus programmatically to the container when it is opened. Otherwise, keyboard users and users of assistive technologies will likely have a hard time finding the newly revealed content - particularly if the container that was opened comes *before* the toggler in the document's structure. We also recommend making sure that the toggler has the `aria-controls` attribute, pointing to the `id` of the content container. In theory, this allows assistive technology users to jump directly from the toggler to the container it controls–but support for this is currently quite patchy.
+
 ## CSS
 
 ### Variables
@@ -727,8 +713,14 @@ When using offcanvas in a dark navbar, be aware that you may need to have a dark
 
 <ScssDocs name="navbar-dark-tokens" file="scss/_navbar.scss" />
 
+### Sass map
+
+We reassign the global `$grid-breakpoints` map to `$navbar-breakpoints` in `scss/_navbar.scss` and use this for our loop to generate the responsive navbar expand/collapse classes. This allows you to customize the breakpoints for navbars independently of the global breakpoints.
+
+<ScssDocs name="navbar-breakpoints" file="scss/_navbar.scss" />
+
 ### Sass loops
 
-[Responsive navbar expand/collapse classes](#responsive-behaviors) (e.g., `.navbar-expand-lg`) are combined with the `$breakpoints` map and generated through a loop in `scss/_navbar.scss`.
+[Responsive navbar expand/collapse classes](#responsive-behaviors) (e.g., `.navbar-expand-lg`) are combined with the `$navbar-breakpoints` map and generated through a loop in `scss/_navbar.scss`.
 
 <ScssDocs name="navbar-expand-loop" file="scss/_navbar.scss" />
index be4a312a45260a1d66de0682817f4511e3bb550c..6bb6db15fda0401db671ca14f42e4ff6f5b7f7e1 100644 (file)
@@ -39,6 +39,21 @@ Classes are used throughout, so your markup can be super flexible. Use `<ul>`s l
     <a class="nav-link disabled" aria-disabled="true">Disabled</a>
   </nav>`} />
 
+Where appropriate, you can also use `<button>` elements instead of `<a>` elements for the nav links. You can even mix and match—for example, by using buttons for dropdowns.
+
+<Example code={`<nav class="nav">
+    <a class="nav-link active" aria-current="page" href="#">Active</a>
+    <a class="nav-link" href="#">Link</a>
+    <button class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Dropdown</button>
+    <ul class="dropdown-menu">
+      <li><a class="dropdown-item" href="#">Action</a></li>
+      <li><a class="dropdown-item" href="#">Another action</a></li>
+      <li><hr class="dropdown-divider"></li>
+      <li><a class="dropdown-item" href="#">Something else here</a></li>
+    </ul>
+    <a class="nav-link disabled" aria-disabled="true">Disabled</a>
+  </nav>`} />
+
 ## Available styles
 
 Change the style of `.nav`s component with modifiers and utilities. Mix and match as needed, or build your own.
index 35d0b5f498f1d218a5227c7618177591680cd8b3..f669b690c1975e3d92c2a6afcb15ed8fd2dc7b15 100644 (file)
@@ -22,7 +22,7 @@ Offcanvas is a sidebar component that can be toggled via JavaScript to appear fr
 
 Below is an offcanvas example that is shown by default (via `.show` on `.offcanvas`). Offcanvas includes support for a header with a close button and an optional body class for some initial `padding`. We suggest that you include offcanvas headers with dismiss actions whenever possible, or provide an explicit dismiss action.
 
-<Example class="bd-example-offcanvas p-0 bg-body-tertiary overflow-hidden" code={`<div class="offcanvas offcanvas-start show" tabindex="-1" id="offcanvas" aria-labelledby="offcanvasLabel">
+<Example class="bd-example-offcanvas p-0 bg-1 overflow-hidden" code={`<div class="offcanvas offcanvas-start show" tabindex="-1" id="offcanvas" aria-labelledby="offcanvasLabel">
     <div class="offcanvas-header">
       <h5 class="offcanvas-title" id="offcanvasLabel">Offcanvas</h5>
       <CloseButton dismiss="offcanvas" />
@@ -126,7 +126,7 @@ When backdrop is set to static, the offcanvas will not close when clicking outsi
 
 Change the appearance of offcanvases with utilities to better match them to different contexts like dark navbars. Here we add `data-bs-theme="dark"` to the `.offcanvas`, and it makes all nested elements within use dark mode.
 
-<Example class="bd-example-offcanvas p-0 bg-body-secondary overflow-hidden" code={`<div class="offcanvas offcanvas-start show" tabindex="-1" id="offcanvasDark" aria-labelledby="offcanvasDarkLabel" data-bs-theme="dark">
+<Example class="bd-example-offcanvas p-0 bg-1 overflow-hidden" code={`<div class="offcanvas offcanvas-start show" tabindex="-1" id="offcanvasDark" aria-labelledby="offcanvasDarkLabel" data-bs-theme="dark">
     <div class="offcanvas-header">
       <h5 class="offcanvas-title" id="offcanvasDarkLabel">Offcanvas</h5>
       <CloseButton dismiss="offcanvasDark" />
@@ -165,14 +165,15 @@ To make a responsive offcanvas, replace the `.offcanvas` base class with a respo
 
 ## Placement
 
-Theres no default placement for offcanvas components, so you must add one of the modifier classes below.
+There's no default placement for offcanvas components, so you must add one of the modifier classes below.
 
 - `.offcanvas-start` places offcanvas on the left of the viewport (shown above)
 - `.offcanvas-end` places offcanvas on the right of the viewport
 - `.offcanvas-top` places offcanvas on the top of the viewport
 - `.offcanvas-bottom` places offcanvas on the bottom of the viewport
+- `.offcanvas-fullscreen` covers the entire viewport
 
-Try the top, right, and bottom examples out below.
+Try the top, right, bottom, and fullscreen examples out below.
 
 <Example code={`<button class="btn btn-primary" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasTop" aria-controls="offcanvasTop">Toggle top offcanvas</button>
 
@@ -210,6 +211,36 @@ Try the top, right, and bottom examples out below.
     </div>
   </div>`} />
 
+<Example code={`<button class="btn btn-primary" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasFullscreen" aria-controls="offcanvasFullscreen">Toggle fullscreen offcanvas</button>
+
+  <div class="offcanvas offcanvas-fullscreen" tabindex="-1" id="offcanvasFullscreen" aria-labelledby="offcanvasFullscreenLabel">
+    <div class="offcanvas-header">
+      <h5 class="offcanvas-title" id="offcanvasFullscreenLabel">Fullscreen offcanvas</h5>
+      <CloseButton dismiss="offcanvas" />
+    </div>
+    <div class="offcanvas-body">
+      <p>This offcanvas covers the entire viewport, useful for full-page menus or modal-like experiences.</p>
+    </div>
+  </div>`} />
+
+## Footer
+
+Add an optional `.offcanvas-footer` for action buttons or other content at the bottom of the offcanvas.
+
+<Example class="bd-example-offcanvas p-0 bg-1 overflow-hidden" code={`<div class="offcanvas offcanvas-start show" tabindex="-1" id="offcanvasFooter" aria-labelledby="offcanvasFooterLabel">
+    <div class="offcanvas-header">
+      <h5 class="offcanvas-title" id="offcanvasFooterLabel">Offcanvas with footer</h5>
+      <CloseButton dismiss="offcanvas" />
+    </div>
+    <div class="offcanvas-body">
+      <p>Content for the offcanvas goes here. The footer will stick to the bottom.</p>
+    </div>
+    <div class="offcanvas-footer">
+      <button type="button" class="btn btn-secondary" data-bs-dismiss="offcanvas">Close</button>
+      <button type="button" class="btn btn-primary">Save changes</button>
+    </div>
+  </div>`} />
+
 ## Accessibility
 
 Since the offcanvas panel is conceptually a modal dialog, be sure to add `aria-labelledby="..."`—referencing the offcanvas title—to `.offcanvas`. Note that you don’t need to add `role="dialog"` since we already add it via JavaScript.
index e9c98251608b29e8a62c8903a41309328ca4ff46..b7d5192c4fc938092bd8ef79883646bacf06379c 100644 (file)
@@ -22,7 +22,7 @@ Scrollspy toggles the `.active` class on anchor (`<a>`) elements when the elemen
 
 Scroll the area below the navbar and watch the active class change. Open the dropdown menu and watch the dropdown items be highlighted as well.
 
-<Example showMarkup={false} code={`<nav id="navbar-example2" class="navbar bg-body-tertiary px-3 mb-3 rounded-2">
+<Example showMarkup={false} code={`<nav id="navbar-example2" class="navbar bg-1 px-3 mb-3 rounded-2">
     <a class="navbar-brand" href="#">Navbar</a>
     <ul class="nav nav-pills">
       <li class="nav-item">
@@ -42,7 +42,7 @@ Scroll the area below the navbar and watch the active class change. Open the dro
       </li>
     </ul>
   </nav>
-  <div class="scrollspy-example bg-body-tertiary p-3 rounded-2" data-bs-spy="scroll" data-bs-target="#navbar-example2" data-bs-root-margin="0px 0px -40%" data-bs-smooth-scroll="true" tabindex="0">
+  <div class="scrollspy-example bg-1 p-3 rounded-2" data-bs-spy="scroll" data-bs-target="#navbar-example2" data-bs-root-margin="0px 0px -40%" data-bs-smooth-scroll="true" tabindex="0">
     <h4 id="scrollspyHeading1">First heading</h4>
     <p>This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It’s repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.</p>
     <h4 id="scrollspyHeading2">Second heading</h4>
@@ -56,7 +56,7 @@ Scroll the area below the navbar and watch the active class change. Open the dro
   </div>`} />
 
 ```html
-<nav id="navbar-example2" class="navbar bg-body-tertiary px-3 mb-3">
+<nav id="navbar-example2" class="navbar bg-1 px-3 mb-3">
   <a class="navbar-brand" href="#">Navbar</a>
   <ul class="nav nav-pills">
     <li class="nav-item">
@@ -76,7 +76,7 @@ Scroll the area below the navbar and watch the active class change. Open the dro
     </li>
   </ul>
 </nav>
-<div data-bs-spy="scroll" data-bs-target="#navbar-example2" data-bs-root-margin="0px 0px -40%" data-bs-smooth-scroll="true" class="scrollspy-example bg-body-tertiary p-3 rounded-2" tabindex="0">
+<div data-bs-spy="scroll" data-bs-target="#navbar-example2" data-bs-root-margin="0px 0px -40%" data-bs-smooth-scroll="true" class="scrollspy-example bg-1 p-3 rounded-2" tabindex="0">
   <h4 id="scrollspyHeading1">First heading</h4>
   <p>...</p>
   <h4 id="scrollspyHeading2">Second heading</h4>
index 6d9f70e9203038f033302d46e3ec2db8de8c2035..f3900197fa362e6f6f3e3485818a7fa0df3adeb2 100644 (file)
@@ -188,7 +188,7 @@ Place toasts with custom CSS as you need them. The top right is often used for n
       </select>
     </div>
   </form>
-  <div aria-live="polite" aria-atomic="true" class="bg-body-secondary position-relative bd-example-toasts rounded-3">
+  <div aria-live="polite" aria-atomic="true" class="bg-1 position-relative bd-example-toasts rounded-3">
     <div class="toast-container p-3" id="toastPlacement">
       <div class="toast">
         <div class="toast-header">
index 4d6573ecb1ec5728eb1a9ad70da89251eb81fc12..2dd7a571ab75a98ee02af72dda834f7c0f89f3fa 100644 (file)
@@ -147,26 +147,28 @@ For example, you can create a “blue theme” with the selector `data-bs-theme=
 
 <ScssDocs name="custom-color-mode" file="site/src/scss/_content.scss" />
 
-<Example showMarkup={false} code={`<div class="bd-example text-body bg-body" data-bs-theme="blue">
-  <div class="h4">Example blue theme</div>
-  <p>Some paragraph text to show how the blue theme might look with written copy.</p>
-
-  <hr class="my-4"/>
-
-  <div class="dropdown">
-    <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButtonCustom" data-bs-toggle="dropdown" aria-expanded="false">
-      Dropdown button
-    </button>
-    <ul class="dropdown-menu" aria-labelledby="dropdownMenuButtonCustom">
-      <li><a class="dropdown-item active" href="#">Action</a></li>
-      <li><a class="dropdown-item" href="#">Action</a></li>
-      <li><a class="dropdown-item" href="#">Another action</a></li>
-      <li><a class="dropdown-item" href="#">Something else here</a></li>
-      <li><hr class="dropdown-divider"></li>
-      <li><a class="dropdown-item" href="#">Separated link</a></li>
-    </ul>
-  </div>
-</div>`} />
+<Example showMarkup={false} code={`<div data-bs-theme="blue">
+    <div class="bd-example fg-body bg-body">
+      <div class="h4">Example blue theme</div>
+      <p>Some paragraph text to show how the blue theme might look with written copy.</p>
+
+      <hr class="my-4"/>
+
+      <div class="dropdown">
+        <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButtonCustom" data-bs-toggle="dropdown" aria-expanded="false">
+          Dropdown button
+        </button>
+        <ul class="dropdown-menu" aria-labelledby="dropdownMenuButtonCustom">
+          <li><a class="dropdown-item active" href="#">Action</a></li>
+          <li><a class="dropdown-item" href="#">Action</a></li>
+          <li><a class="dropdown-item" href="#">Another action</a></li>
+          <li><a class="dropdown-item" href="#">Something else here</a></li>
+          <li><hr class="dropdown-divider"></li>
+          <li><a class="dropdown-item" href="#">Separated link</a></li>
+        </ul>
+      </div>
+    </div>
+  </div>`} />
 
 ```html
 <div data-bs-theme="blue">
index aaf1de0af47036db6694215499260dccdd52ee3f..343da0881030b2c0bd3a6d9d7b2afd1f39bbdf4f 100644 (file)
@@ -16,11 +16,11 @@ export const additionalColors = [
 
 There are 13 shades of 16 colors in Bootstrap's new color system, meaning we have 208 colors to work with when using Bootstrap.
 
-All Bootstrap colors are available as Sass variables and a Sass map in `scss/_variables.scss` file. To avoid increased file sizes, we don’t create text or background color classes for each of these variables. Instead, we choose a subset of these colors for a [theme palette](#theme-colors).
+All Bootstrap colors are available as Sass variables and a Sass map in `scss/_variables.scss` file. To avoid increased file sizes, we don’t create text or background color classes for each of these variables. Instead, we choose a subset of these colors for a [theme palette]([[docsref:/customize/theme]]).
 
 Be sure to monitor contrast ratios as you customize colors. As shown below, we’ve added three contrast ratios to each of the main colors—one for the swatch’s current colors, one for against white, and one for against black.
 
-<div class="grid gap-0" style={{gridTemplateColumns: 'repeat(13, 1fr)', gap: '4px', minWidth: '0'}}>
+<div class="grid bd-colors-grid">
   {getData('colors').map((color) => {
      return (
        <div class="d-contents grid-cols-fill">
@@ -51,68 +51,65 @@ Be sure to monitor contrast ratios as you customize colors. As shown below, we
    </div>
 </div>
 
-<Callout>
-  @mdo-do: Move this content to Theme page most likely. Replace with how to modify color variables.
-</Callout>
+## How it works
 
-### Using the new colors
+Bootstrap generates its colors from a series of Sass variables and a Sass map called `$colors` in `scss/_colors.scss`. These are our base colors—blue, indigo, violet, etc—and are used to generate the tints and shades of each color you see above. You can customize these colors by adding or removing colors from the `$colors` map.
 
-These new colors are accessible via CSS variables and utility classes—like `--bs-primary-bg-subtle` and `.bg-primary-subtle`—allowing you to compose your own CSS rules with the variables, or to quickly apply styles via classes. The utilities are built with the color’s associated CSS variables, and since we customize those CSS variables for dark mode, they are also adaptive to color mode by default.
+### Default colors
 
-<Example code={`<div class="p-3 text-primary-emphasis bg-primary-subtle border border-primary-subtle rounded-3">
-    Example element with utilities
-  </div>`} />
+Below is our default list of colors. Colors are unique in Bootstrap in that they're still Sass variables by default. In addition, they're in `oklch()` format, which is a modern color space that is designed to be perceptually uniform.
 
-### Theme colors
+<ScssDocs name="colors-list" file="scss/_colors.scss" />
 
-We use a subset of all colors to create a smaller color palette for generating color schemes, also available as Sass variables and a Sass map in Bootstrap’s `scss/_variables.scss` file.
+The above colors are then turned into a Sass map called `$colors`.
 
-<div class="row">
-  {getData('theme-colors').map((themeColor) => {
-    return (
-      <div class="col-md-4">
-        <div class={`p-3 mb-3 text-bg-${themeColor.name} rounded-3`}>{themeColor.title}</div>
-      </div>
-    )
-  })}
-</div>
+<ScssDocs name="colors-map" file="scss/_colors.scss" />
 
-All these colors are available as a nested Sass map, `$theme-colors`, in `scss/_theme.scss`. Check out [our Sass maps and loops docs]([[docsref:/customize/sass#maps-and-loops]]) for how to modify these colors.
+### Color mixing
 
-### Notes on Sass
+As mentioned already, we generate lighter (tints) and darker (shades) versions of each color using the `color-mix()` function. This allows us to quickly and easily generate a full scale of colors.
 
-Sass cannot programmatically generate variables, so we manually created variables for every tint and shade ourselves. We specify the midpoint value (e.g., `$blue-500`) and use custom color functions to tint (lighten) or shade (darken) our colors via Sass’s `mix()` color function.
+The tint and shade stops, the `color-mix()` color space, and the mix colors are all customizable.
 
-Using `mix()` is not the same as `lighten()` and `darken()`—the former blends the specified color with white or black, while the latter only adjusts the lightness value of each color. The result is a much more complete suite of colors, as [shown in this CodePen demo](https://codepen.io/emdeoh/pen/zYOQOPB).
+<ScssDocs name="color-mix-options" file="scss/_colors.scss" />
 
-Our `tint-color()` and `shade-color()` functions use `mix()` alongside our `$theme-color-interval` variable, which specifies a stepped percentage value for each mixed color we produce. See the `scss/_functions.scss` and `scss/_variables.scss` files for the full source code.
+Those options and the `$colors` map are used to generate a `$color-tokens` map, which is output as CSS custom properties on `:root`.
 
-## Color Sass maps
+<ScssDocs name="color-tokens" file="scss/_colors.scss" />
 
-Bootstrap’s source Sass files include three maps to help you quickly and easily loop over a list of colors and their hex values.
+## Customizing
 
-- `$colors` lists all our available base (`500`) colors
-- `$theme-colors` is a nested map of semantically named theme colors (defined in `scss/_theme.scss`)
+You can customize the colors by adding or removing colors from the `$colors` map. You can also customize the tint and shade stops, the `color-mix()` color space, and the mix colors. Say you want to add another blue-gray color, like slate.
 
-Within `scss/_variables.scss`, you’ll find Bootstrap’s color variables and Sass map. Here’s an example of the `$colors` Sass map:
+1. Create a new Sass variable for the new color, in `oklch()` format.
+2. Add the new color to the `$colors` map.
+3. Recompile source Sass to generate the new colors.
 
-{/*<ScssDocs name="colors-map" file="scss/_variables.scss" />*/}
+Here's how that would look:
 
-Add, remove, or modify values within the map to update how they’re used in many other components. Unfortunately at this time, not *every* component utilizes this Sass map. Future updates will strive to improve upon this. Until then, plan on making use of the `${color}` variables and this Sass map.
+```scss
+$slate: oklch(55% 0.07 260);
 
-### Example
+@use "bootstrap" as * with (
+  $colors: (
+    "slate": $slate,
+  ),
+);
+```
 
-Here’s how you can use these in your Sass:
+To remove a color, set the value to `null`.
 
 ```scss
-.alpha { color: $purple; }
-.beta {
-  color: $yellow-300;
-  background-color: $indigo-900;
-}
+@use "bootstrap" as * with (
+  $colors: (
+    "pewter": null,
+  ),
+);
 ```
 
-[Color]([[docsref:/utilities/colors]]) and [background]([[docsref:/utilities/background]]) utility classes are also available for setting `color` and `background-color` using the `500` color values.
+<Callout type="warning">
+mdo-do: Update the content below
+</Callout>
 
 ## Generating utilities
 
index e845017de35deb83b13b76be93bcf2a14366fc8d..3d75153cd953c108b90562ccd883fc701699ea93 100644 (file)
@@ -386,10 +386,6 @@ Base colors are defined as `oklch()` values and expanded into full scales via `c
 
 You can reference any color scale step as a CSS custom property: `var(--blue-500)`, `var(--red-200)`, `var(--gray-900)`, etc.
 
-Bootstrap also includes `tint-color()`, `shade-color()`, and `shift-color()` Sass functions for mixing colors with white or black:
-
-<ScssDocs name="color-functions" file="scss/_functions.scss" />
-
 ### Color contrast
 
 In order to meet the [Web Content Accessibility Guidelines (WCAG)](https://www.w3.org/TR/WCAG/) contrast requirements, authors **must** provide a minimum [text color contrast of 4.5:1](https://www.w3.org/TR/WCAG/#contrast-minimum) and a minimum [non-text color contrast of 3:1](https://www.w3.org/TR/WCAG/#non-text-contrast), with very few exceptions.
index 77fe3e2d622c08d343174b2dc5513528d46d14b4..f016150fd71299fda65508d58fb966e87deee5a3 100644 (file)
@@ -79,7 +79,7 @@ And within each semantic theme color, you'll find the following keys, most of wh
 
 Every token is available as a CSS variable, and most are then consumed by our utilities and components. So for the `primary` color, you have the following colors:
 
-<div class="grid gap-0" style={{gridTemplateColumns: 'repeat(9, 1fr)', gap: '8px', minWidth: '0'}}>
+<div class="grid bd-theme-colors-grid small">
   {getData('theme-colors').map((color) => {
     return (
       <div class="d-contents grid-cols-fill">
@@ -194,6 +194,77 @@ Here are some examples showing them together. Note
     </div>
   </div>`} />
 
+## Color modes
+
+Bootstrap's theme and layer colors are **color mode adaptive** thanks to our use of CSS’s `light-dark()` function. This lets us specify two colors at once for a component, one for light and one for dark mode. These colors are switched by using the `data-bs-theme` attribute to set the `color-scheme` property to `light` or `dark`.
+
+By default, `color-scheme: light dark` is set on the `:root` element, meaning Bootstrap respects the user’s system preference. You can override this at any level by setting `data-bs-theme="light"` or `data-bs-theme="dark"` on any element to force a specific mode for that subtree. Depending on the element, you may need to include `.bg-body`, `.fg-body`, or similar utilities to ensure correct color application.
+
+Here's the same set of components rendered in both light and dark modes side by side. Note the use of `.bg-body` for the backgrounds. Without it, the light mode demo wouldn’t have a white background—just light mode components on a dark background.
+
+<Example class="p-0 overflow-hidden" code={`<div class="row g-0">
+    <div class="col-md-6 vstack gap-3 p-4 bg-body" data-bs-theme="light">
+      <h6 class="fw-semibold">Light mode</h6>
+      <div class="card">
+        <div class="card-body">
+          <h5 class="card-title">Card title</h5>
+          <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+        </div>
+      </div>
+      <div class="alert theme-primary">This is a primary alert.</div>
+      <input type="text" class="form-control" placeholder="Form control">
+      <div class="checkgroup">
+        <div class="check">
+          <input type="checkbox" id="checkLabel" />
+          <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'>
+            <path class="checked" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m4 8 3 3 5-5'/>
+            <path class="indeterminate" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4.5 8.5h6'/>
+          </svg>
+        </div>
+        <label class="form-label" for="checkLabel">Example new checkbox</label>
+      </div>
+      <div class="vstack gap-2">
+        <button type="button" class="btn btn-solid theme-primary">Primary</button>
+        <button type="button" class="btn btn-subtle theme-secondary">Secondary</button>
+      </div>
+    </div>
+    <div class="col-md-6 vstack gap-3 p-4 bg-body" data-bs-theme="dark">
+      <h6 class="fw-semibold">Dark mode</h6>
+      <div class="card">
+        <div class="card-body">
+          <h5 class="card-title">Card title</h5>
+          <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+        </div>
+      </div>
+      <div class="alert theme-primary">This is a primary alert.</div>
+      <input type="text" class="form-control" placeholder="Form control">
+      <div class="checkgroup">
+        <div class="check">
+          <input type="checkbox" id="checkDarkLabel" />
+          <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'>
+            <path class="checked" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m4 8 3 3 5-5'/>
+            <path class="indeterminate" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4.5 8.5h6'/>
+          </svg>
+        </div>
+        <label class="form-label" for="checkDarkLabel">Example new checkbox</label>
+      </div>
+      <div class="vstack gap-2">
+        <button type="button" class="btn btn-solid theme-primary">Primary</button>
+        <button type="button" class="btn btn-subtle theme-secondary">Secondary</button>
+      </div>
+    </div>
+  </div>`} />
+
+Under the hood, token values like `--bs-primary-text` resolve differently depending on the active color mode. For example, in our `_theme.scss`, the primary text color is defined as:
+
+```scss
+"text": light-dark(var(--blue-600), var(--blue-400)),
+```
+
+When `data-bs-theme="light"` is active (or the user's system preference is light), this resolves to `var(--blue-600)`. In dark mode, it resolves to `var(--blue-400)`—a lighter shade that maintains readability against dark backgrounds. The same pattern applies to backgrounds, borders, and all other adaptive tokens.
+
+For more details on toggling color modes globally, building custom modes, and JavaScript toggler examples, see the [color modes documentation]([[docsref:/customize/color-modes/]]).
+
 ## Theme utility classes
 
 We generate theme utility classes from the `$theme-colors` Sass map that make all theme color tokens available as CSS variables. We use these in our component variants to allow for quick theming with a single, global class. This requires components to support theme colors, and not every component does.
index 24cc93ba09e75fc2ba521526b5ef8e6a87c7672d..36f93b5c0ebe5476f6a7944c3a5818dc7a631d54 100644 (file)
@@ -43,22 +43,22 @@ And with [vertical rules]([[docsref:/helpers/vertical-rule]]):
     <div class="p-2">Third item</div>
   </div>`} />
 
-## Examples
+## Responsive
 
-Use `.vstack` to stack buttons and other elements:
+Consider this example of stacking buttons on narrow mobile devices, and making them horizontal on larger devices.
 
 <Example code={`<div class="vstack gap-2 col-md-5 mx-auto">
     <button type="button" class="btn-solid theme-primary">Save changes</button>
     <button type="button" class="btn-outline theme-secondary">Cancel</button>
   </div>`} />
 
-Create an inline form with `.hstack`:
+Here's how you'd do it with responsive stacks, which are based on a container media queries. Wrap the stack in an extra element or add `.stack-container` to an existing parent to give the stack a "container" to adapt its layout from.
 
-<Example code={`<div class="hstack gap-3">
-    <input class="form-control me-auto" type="text" placeholder="Add your item here..." aria-label="Add your item here...">
-    <button type="button" class="btn-solid theme-secondary">Submit</button>
-    <div class="vr my-2"></div>
-    <button type="button" class="btn-subtle theme-danger">Reset</button>
+<ResizableExample code={`<div class="stack-container">
+    <div class="vstack gap-2 hstack-sm">
+      <button type="button" class="btn-solid theme-primary">Save changes</button>
+      <button type="button" class="btn-outline theme-secondary">Cancel</button>
+    </div>
   </div>`} />
 
 ## CSS
index c99333e771b1b57f408033001182da01b320f714..e61b9db5d194bd669aa899311fdb8c63f494441f 100644 (file)
@@ -29,7 +29,7 @@ Most custom components do not have `position: relative` by default, so we need t
     </div>
   </div>`} />
 
-<Example code={`<div class="row g-0 bg-body-secondary position-relative">
+<Example code={`<div class="row g-0 bg-1 position-relative">
     <div class="col-md-6 mb-md-0 p-md-4">
       <Placeholder width="100%" height="200" class="w-100" text={false} title="Generic placeholder image" />
     </div>
index 50c74bf2d8c2e7d84031aa96479937a5361c1a9d..79bc2b1e0ced0efe702e6c8fe7b6e1080e3038e7 100644 (file)
@@ -165,3 +165,153 @@ Which results in:
 // Apply styles starting from medium devices and up to extra large devices
 @media (width >= 768px) and (width < 1200px) { ... }
 ```
+
+## Container queries
+
+In addition to viewport-based media queries, Bootstrap uses [container queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries) for certain components. Container queries allow elements to respond to the size of a parent element rather than the viewport, enabling more flexible and modular responsive behavior.
+
+Container queries use the same breakpoint values as viewport media queries.
+
+### Usage in Bootstrap
+
+The following components use container queries:
+
+- **[Navbar]([[docsref:/components/navbar]])** — Uses container queries for its responsive expand behavior. The `.navbar` element is set as a query container, and the `.navbar-expand-*` classes use `@container` queries instead of `@media` queries. This means the navbar responds to its own width rather than the viewport width, making it more adaptable when placed in different layout contexts (e.g., within a sidebar or constrained container).
+
+- **[Stepper]([[docsref:/components/stepper]])** — The `.stepper-overflow` wrapper uses `container-type: inline-size` to establish a containment context for the horizontally scrolling stepper pattern.
+
+Here's how the navbar implements container queries:
+
+```scss
+// The navbar is defined as a query container
+.navbar {
+  container-type: inline-size;
+}
+
+// Responsive classes use container queries
+.navbar-expand-lg {
+  @container (min-width: 1024px) {
+    // Expanded navbar styles...
+  }
+}
+```
+
+### Setting a container
+
+Use the `set-container()` mixin to establish an element as a query container:
+
+```scss
+// Default: inline-size containment
+.my-component {
+  @include set-container();
+  // Output: container-type: inline-size;
+}
+
+// With explicit type
+.my-component {
+  @include set-container(size);
+  // Output: container-type: size;
+}
+
+// With a name (uses shorthand property)
+.my-component {
+  @include set-container(inline-size, sidebar);
+  // Output: container: sidebar / inline-size;
+}
+```
+
+### Container query mixins
+
+Similar to the `media-breakpoint-*` mixins for viewport-based media queries, Bootstrap provides container query mixins that use the same breakpoint values and range syntax.
+
+#### Min-width
+
+Use `container-breakpoint-up()` to apply styles when the container is at least the given breakpoint width:
+
+```scss
+.my-component {
+  @include set-container();
+}
+
+.my-component-child {
+  // …
+
+  @include container-breakpoint-up(md) {
+    // Styles for when container is at least 768px wide
+  }
+
+  @include container-breakpoint-up(lg) {
+    // Styles for when container is at least 1024px wide
+  }
+}
+```
+
+These mixins use the same modern range syntax as media queries. For example:
+
+```scss
+@container (width >= 768px) { ... }
+@container (width >= 1024px) { ... }
+```
+
+#### Max-width
+
+Use `container-breakpoint-down()` to apply styles when the container is narrower than the given breakpoint:
+
+```scss
+// Sass usage
+@include container-breakpoint-down(lg) { ... }
+
+// Compiled CSS
+@container (width < 1024px) { ... }
+```
+
+#### Single breakpoint
+
+Use `container-breakpoint-only()` to target a single breakpoint range:
+
+```scss
+// Sass usage
+@include container-breakpoint-only(md) { ... }
+
+// Compiled CSS
+@container (width >= 768px) and (width < 1024px) { ... }
+```
+
+#### Between breakpoints
+
+Use `container-breakpoint-between()` to span multiple breakpoint widths:
+
+```scss
+// Sass usage
+@include container-breakpoint-between(md, xl) { ... }
+
+// Compiled CSS
+@container (width >= 768px) and (width < 1280px) { ... }
+```
+
+### Named containers
+
+All container query mixins accept an optional container name parameter for querying a specific ancestor container. This is useful when you have nested containers:
+
+```scss
+.sidebar {
+  @include set-container(inline-size, sidebar);
+}
+
+.main-content {
+  @include set-container(inline-size, main);
+}
+
+// Query the sidebar container specifically, even if nested inside main
+.widget {
+  @include container-breakpoint-up(md, sidebar) {
+    // …
+  }
+}
+```
+
+The compiled output includes the container name:
+
+```scss
+@container sidebar (width >= 768px) { ... }
+```
index ce60ddf26520bfaa8612b66c6b9438a64d93dd72..953bff3a8c88d5f2fe57047df39f3f941a4962de 100644 (file)
@@ -39,19 +39,19 @@ Adjust the `overflow` property on the fly with four default values and classes.
 Adjust the `overflow-x` property to affect the overflow of content horizontally.
 
 <div class="bd-example d-md-flex">
-  <div class="overflow-x-auto p-3 mb-3 mb-md-0 me-md-3 bg-body-tertiary w-100" style="max-width: 200px; max-height: 100px; white-space: nowrap;">
+  <div class="overflow-x-auto p-3 mb-3 mb-md-0 me-md-3 bg-1 w-100" style="max-width: 200px; max-height: 100px; white-space: nowrap;">
     <div><code>.overflow-x-auto</code> example on an element</div>
     <div> with set width and height dimensions.</div>
   </div>
-  <div class="overflow-x-hidden p-3 mb-3 mb-md-0 me-md-3 bg-body-tertiary w-100" style="max-width: 200px; max-height: 100px;white-space: nowrap;">
+  <div class="overflow-x-hidden p-3 mb-3 mb-md-0 me-md-3 bg-1 w-100" style="max-width: 200px; max-height: 100px;white-space: nowrap;">
     <div><code>.overflow-x-hidden</code> example</div>
     <div>on an element with set width and height dimensions.</div>
   </div>
-  <div class="overflow-x-visible p-3 mb-3 mb-md-0 me-md-3 bg-body-tertiary w-100" style="max-width: 200px; max-height: 100px;white-space: nowrap;">
+  <div class="overflow-x-visible p-3 mb-3 mb-md-0 me-md-3 bg-1 w-100" style="max-width: 200px; max-height: 100px;white-space: nowrap;">
     <div><code>.overflow-x-visible</code> example </div>
     <div>on an element with set width and height dimensions.</div>
   </div>
-  <div class="overflow-x-scroll p-3 bg-body-tertiary w-100" style="max-width: 200px; max-height: 100px;white-space: nowrap;">
+  <div class="overflow-x-scroll p-3 bg-1 w-100" style="max-width: 200px; max-height: 100px;white-space: nowrap;">
     <div><code>.overflow-x-scroll</code> example on an element</div>
     <div> with set width and height dimensions.</div>
   </div>
@@ -69,16 +69,16 @@ Adjust the `overflow-x` property to affect the overflow of content horizontally.
 Adjust the `overflow-y` property to affect the overflow of content vertically.
 
 <div class="bd-example d-md-flex">
-  <div class="overflow-y-auto p-3 mb-3 mb-md-0 me-md-3 bg-body-tertiary w-100" style="max-width: 200px; max-height: 100px;">
+  <div class="overflow-y-auto p-3 mb-3 mb-md-0 me-md-3 bg-1 w-100" style="max-width: 200px; max-height: 100px;">
     <code>.overflow-y-auto</code> example on an element with set width and height dimensions.
   </div>
-  <div class="overflow-y-hidden p-3 mb-3 mb-md-0 me-md-3 bg-body-tertiary w-100" style="max-width: 200px; max-height: 100px;">
+  <div class="overflow-y-hidden p-3 mb-3 mb-md-0 me-md-3 bg-1 w-100" style="max-width: 200px; max-height: 100px;">
     <code>.overflow-y-hidden</code> example on an element with set width and height dimensions.
   </div>
-  <div class="overflow-y-visible p-3 mb-3 mb-md-0 me-md-3 bg-body-tertiary w-100" style="max-width: 200px; max-height: 100px;">
+  <div class="overflow-y-visible p-3 mb-3 mb-md-0 me-md-3 bg-1 w-100" style="max-width: 200px; max-height: 100px;">
     <code>.overflow-y-visible</code> example on an element with set width and height dimensions.
   </div>
-  <div class="overflow-y-scroll p-3 bg-body-tertiary w-100" style="max-width: 200px; max-height: 100px;">
+  <div class="overflow-y-scroll p-3 bg-1 w-100" style="max-width: 200px; max-height: 100px;">
     <code>.overflow-y-scroll</code> example on an element with set width and height dimensions.
   </div>
 </div>
index e2f064ee3e9dd2b26208d9b87131e95745421415..5f3abbba0939a8d08707255005c0f438576507ed 100644 (file)
@@ -10,10 +10,10 @@ utility:
 
 While shadows on components are disabled by default in Bootstrap and can be enabled via `$enable-shadows`, you can also quickly add or remove a shadow with our `box-shadow` utility classes. Includes support for `.shadow-none` and three default sizes (which have associated variables to match).
 
-<Example class="overflow-hidden" code={`<div class="shadow-none p-3 mb-5 bg-body-tertiary rounded">No shadow</div>
-<div class="shadow-sm p-3 mb-5 bg-body-tertiary rounded">Small shadow</div>
-<div class="shadow p-3 mb-5 bg-body-tertiary rounded">Regular shadow</div>
-<div class="shadow-lg p-3 mb-5 bg-body-tertiary rounded">Larger shadow</div>`} />
+<Example class="overflow-hidden" code={`<div class="shadow-none p-3 mb-5 bg-1 rounded">No shadow</div>
+<div class="shadow-sm p-3 mb-5 bg-1 rounded">Small shadow</div>
+<div class="shadow p-3 mb-5 bg-1 rounded">Regular shadow</div>
+<div class="shadow-lg p-3 mb-5 bg-1 rounded">Larger shadow</div>`} />
 
 ## CSS
 
index a4322eeb83dc0dc643d90111ec05694abca17d0c..27deefec38c0d4c36025b23f555f8c39e105b148 100644 (file)
@@ -16,6 +16,7 @@ import { getData } from '@libs/data'
 import GitHubIcon from '@components/icons/GitHubIcon.astro'
 import MdnIcon from '@components/icons/MdnIcon.astro'
 import CssTricksIcon from '@components/icons/CssTricksIcon.astro'
+import CloseButton from '@components/shortcodes/CloseButton.astro'
 
 interface NavigationPage {
   title: string
@@ -93,12 +94,7 @@ if (currentPageIndex < allPages.length - 1) {
       <div class="offcanvas-lg offcanvas-start" tabindex="-1" id="bdSidebar" aria-labelledby="bdSidebarOffcanvasLabel">
         <div class="offcanvas-header border-bottom">
           <h5 class="offcanvas-title" id="bdSidebarOffcanvasLabel">Browse docs</h5>
-          <button
-            type="button"
-            class="btn-close"
-            data-bs-dismiss="offcanvas"
-            aria-label="Close"
-            data-bs-target="#bdSidebar"></button>
+          <CloseButton dismiss="offcanvas" target="#bdSidebar" />
         </div>
 
         <div class="offcanvas-body">
@@ -170,7 +166,7 @@ if (currentPageIndex < allPages.length - 1) {
         {
           frontmatter.toc && headings && (
             <button
-              class="btn btn-link p-lg-0 mb-2 mb-lg-0 text-decoration-none bd-toc-toggle d-flex align-items-center d-lg-none"
+              class="btn-sm btn-subtle mb-2 bd-toc-toggle d-lg-none"
               type="button"
               data-bs-toggle="collapse"
               data-bs-target="#tocContents"
index 09741954df62c333e929a411706b9f0150477880..db6466cec2080c461188b286049335bf33e84cdc 100644 (file)
       margin-top: 2rem;
     }
 
-    > .form-control {
-      + .form-control {
-        margin-top: .5rem;
-      }
-    }
-
-    > .nav + .nav,
-    > .alert + .alert,
-    > .navbar + .navbar,
-    > .progress + .progress {
-      margin-top: $spacer;
-    }
+    // > .form-control {
+    //   + .form-control {
+    //     margin-top: .5rem;
+    //   }
+    // }
+
+    // > .nav + .nav,
+    // > .alert + .alert,
+    // > .navbar + .navbar,
+    // > .progress + .progress {
+    //   margin-top: $spacer;
+    // }
 
     > .dropdown-menu {
       max-width: 12rem;
       position: static;
       display: block;
       height: 200px;
+      margin: var(--offcanvas-inset);
       visibility: visible;
       transform: translate(0);
     }
     }
   }
 
+  //
+  // Resizable examples
+  //
+
+  .bd-example-resizable {
+    position: relative;
+  }
+
+  .bd-resizable-container {
+    max-width: 100%;
+    padding: 1rem;
+    overflow: hidden;
+    resize: horizontal;
+    background-color: var(--bs-bg-body);
+    border: 1px dashed var(--bs-border-color);
+    @include border-radius(var(--bs-border-radius));
+  }
+
   //
   // Code snippets
   //
     margin-inline-end: 0;
   }
 
+
+  .bd-colors-grid {
+    grid-template-columns: repeat(7, 1fr);
+    gap: 4px;
+    min-width: 0;
+
+    @include media-breakpoint-up(xl) {
+      grid-template-columns: repeat(13, 1fr);
+    }
+  }
+
+  .bd-theme-colors-grid {
+    grid-template-columns: repeat(5, 1fr);
+    gap: 8px;
+    min-width: 0;
+
+    @include media-breakpoint-up(xl) {
+      grid-template-columns: repeat(9, 1fr);
+    }
+  }
 }
index b7bd3f71077050b1e30984cabaca95fbd7a778da..e2c45feae6e8c0ab151389c7e7dc20d704d0efcb 100644 (file)
   }
 
   // scss-docs-start custom-color-mode
-  // [data-bs-theme="blue"] {
-  //   --bs-fg-body: var(--bs-white);
-  //   --bs-bg-body: var(--bs-blue);
-  //   --bs-bg-3: #{$blue-600};
-
-  //   .dropdown-menu {
-  //     --bs-dropdown-bg: #{color.mix($blue-500, $blue-600)};
-  //     --bs-dropdown-link-active-bg: #{$blue-700};
-  //   }
-
-  //   .btn-secondary {
-  //     --bs-btn-bg: #{color.mix($gray-600, $blue-400)};
-  //     --bs-btn-border-color: #{rgba($white, .25)};
-  //     --bs-btn-hover-bg: #{color.scale(color.mix($gray-600, $blue-400), $lightness: -5%)}; // stylelint-disable-line scss/at-function-named-arguments
-  //     --bs-btn-hover-border-color: #{rgba($white, .25)};
-  //     --bs-btn-active-bg: #{color.scale(color.mix($gray-600, $blue-400), $lightness: -10%)}; // stylelint-disable-line scss/at-function-named-arguments
-  //     --bs-btn-active-border-color: #{rgba($white, .5)};
-  //     --bs-btn-focus-border-color: #{rgba($white, .5)};
-  //     --bs-btn-focus-box-shadow: 0 0 0 .25rem rgba(255, 255, 255, .2);
-  //   }
-  // }
+  [data-bs-theme="blue"] {
+    --fg-body: var(--white);
+    --bg-body: var(--blue);
+    --bg-3: var(--blue-600);
+
+    .dropdown-menu {
+      --dropdown-bg: color-mix(in lab, var(--blue-500), var(--blue-600));
+      --dropdown-link-active-bg: var(--blue-700);
+    }
+
+    .btn-secondary {
+      --btn-bg: color-mix(in lab, var(--gray-600), var(--blue-400));
+      --btn-border-color: color-mix(in lab, var(--fg-body) 25%, transparent);
+      --btn-hover-bg: color-mix(in lab, var(--gray-600), var(--blue-400));
+      --btn-hover-border-color: color-mix(in lab, var(--fg-body) 25%, transparent);
+      --btn-active-bg: color-mix(in lab, color-mix(in lab, var(--gray-600), var(--blue-400)), var(--black) 10%);
+      --btn-active-border-color: color-mix(in lab, var(--fg-body), transparent);
+      --btn-focus-border-color: color-mix(in lab, var(--fg-body), transparent);
+      --btn-focus-box-shadow: 0 0 0 .25rem rgb(255 255 255 / .2);
+    }
+  }
   // scss-docs-end custom-color-mode
 }
index 9e3b02d41f1803ddf50103615740ae9d581955d4..c1b6bd7e4f2b8de96290e8ca02ff78d3f64eb0ac 100644 (file)
@@ -1,7 +1,5 @@
-@use "../../../scss/config" as *;
 @use "../../../scss/colors" as *;
 @use "../../../scss/mixins" as *;
-@use "../../../scss/variables" as *;
 @use "../../../scss/layout/breakpoints" as *;
 
 :root {
       }
     }
 
+    .nav {
+      --bs-nav-link-hover-bg: transparent;
+      --bs-nav-link-active-bg: transparent;
+    }
+
     .navbar-toggler,
     .nav-link {
-      padding-inline: $spacer * .25;
+      font-size: var(--font-size-sm);
+      // padding-inline: $spacer * .25;
       // color: rgba($white, .85);
 
       // &:hover,
index 858b11dc9d12b7512df6eed856f92e311589e6a6..7530544649ba7ef30145f47aaed73402e6fc62ad 100644 (file)
@@ -20,7 +20,9 @@ export declare global {
   export const Example: typeof import('@shortcodes/Example.astro').default
   export const JsDismiss: typeof import('@shortcodes/JsDismiss.astro').default
   export const JsDocs: typeof import('@shortcodes/JsDocs.astro').default
+  export const NavbarPlacementPlayground: typeof import('@shortcodes/NavbarPlacementPlayground.astro').default
   export const Placeholder: typeof import('@shortcodes/Placeholder.astro').default
+  export const ResizableExample: typeof import('@shortcodes/ResizableExample.astro').default
   export const ScssDocs: typeof import('@shortcodes/ScssDocs.astro').default
   export const StepperPlayground: typeof import('@shortcodes/StepperPlayground.astro').default
   export const Swatch: typeof import('@shortcodes/Swatch.astro').default