* 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
"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": {
"mouseleave",
"navbars",
"navs",
+ "navoverflow",
"Neue",
"noindex",
"Noto",
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'
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'
Datepicker,
Dialog,
Dropdown,
+ NavOverflow,
Offcanvas,
Strength,
OtpInput,
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
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'
}
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()
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
dispose() {
this._disposeFloating()
+ this._restoreMenuToOriginalParent()
this._disposeMediaQueryListeners()
this._closeAllSubmenus()
this._clearAllSubmenuTimeouts()
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)
referenceElement,
this._menu,
floatingConfig.placement,
- floatingConfig.middleware
+ floatingConfig.middleware,
+ floatingConfig.strategy
)
}
_getFloatingConfig(placement, middleware) {
const defaultConfig = {
placement,
- middleware
+ middleware,
+ strategy: this._config.strategy
}
return {
}
}
+ _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
}
const { x, y, placement: finalPlacement } = await computePosition(
reference,
floating,
- { placement, middleware }
+ { placement, middleware, strategy }
)
if (!floating.isConnected) {
}
Object.assign(floating.style, {
- position: 'absolute',
+ position: strategy,
left: `${x}px`,
top: `${y}px`,
margin: '0'
--- /dev/null
+/**
+ * --------------------------------------------------------------------------
+ * 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
}, 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', () => {
--- /dev/null
+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')
+ }
+ })
+ })
+})
// 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);
+}
// scss-docs-start spacer-variables-maps
$spacer: 1rem !default;
$spacers: (
+ -2: $spacer * -.5,
+ -1: $spacer * -.25,
0: 0,
1: $spacer * .25,
2: $spacer * .5,
--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),
// 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
}
}
-// 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) {
@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
--- /dev/null
+// 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;
+ }
+}
--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),
.nav-item {
display: flex;
-
}
.nav-link {
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));
-@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
$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,
--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;
// 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;
}
}
- // 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;
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>"))};
- }
- }
- }
}
-@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 *;
// 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,
// 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
);
}
@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) & {
}
}
+ // 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) & {
}
}
+ // 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;
}
}
+ // Above breakpoint - show content inline (for responsive offcanvas)
@if not ($infix == "") {
@include media-breakpoint-up($next) {
--offcanvas-height: auto;
}
.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
}
}
}
}
+ // 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;
.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));
}
}
+ // 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);
+ }
}
--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},
// 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),
--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,
color-scheme: light dark;
}
+
+[data-bs-theme="dark"] {
+ color-scheme: dark;
+}
+
+[data-bs-theme="light"] {
+ color-scheme: light;
+}
-@use "colors" as *;
@use "config" as *;
@use "functions" as *;
// 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
// $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;
@forward "dropdown";
@forward "list-group";
@forward "nav";
+@forward "nav-overflow";
@forward "navbar";
@forward "offcanvas";
@forward "pagination";
small,
.small {
- font-size: var(--small-font-size, var(--font-size-sm));
+ font-size: var(--small-font-size, 87.5%);
}
// Mark
--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),
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);
--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),
--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>"))},
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;
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;
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;
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 {
-// 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
}
@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
// 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; }
}
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
<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>
<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>
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
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">
<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">
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']
<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;" />}
<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"
</button>
</div>
+ {/* Main navigation - offcanvas on mobile, inline on desktop */}
<div
class="offcanvas-lg offcanvas-end flex-grow-1"
tabindex="-1"
>
<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>
<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>
<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" />
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>
* 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>
{nestedInExample ? (
<>
{!noToolbar && (
- <div class="hstack highlight-toolbar">
+ <div class="hstack highlight-toolbar align-items-center">
{highlightedTabs ? (
<div class="code-tabs">
{highlightedTabs.map((tab, index) => (
) : (
<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) => (
--- /dev/null
+---
+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>
--- /dev/null
+---
+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>
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>`)} />
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>`)} />
</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.
<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">
| --- | --- | --- | --- |
| `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>
--- /dev/null
+---
+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`)
+})
+```
---
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
-Here’s 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.
- `.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
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>
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">
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">
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>
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>`} />
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"/>
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">
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>
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>
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
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>
<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>
## Containers
-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).
+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>
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>
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>
</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
<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" />
<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.
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" />
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" />
## Placement
-There’s 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>
</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.
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">
</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>
</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">
</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>
</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">
<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">
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">
</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
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.
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">
</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.
<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
</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>
// 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) { ... }
+```
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>
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>
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
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
<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">
{
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"
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);
+ }
+ }
}
}
// 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
}
-@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,
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