@use 'sass:map'; @use '../config' as *; @mixin navbar-vertical-nav { // Applies to every navbar-nav in the vertical navbar — the menu inside // .navbar-collapse and e.g. a .navbar-footer list outside of it. .navbar-nav { // Sidebar icons carry more meaning than inline tab icons, so they get // stronger contrast than the global 50% $nav-link-icon-color. Derived // from --navbar-color, which is defined at this scope (unlike // --nav-link-color, which only exists inside .nav). --nav-link-icon-color: #{color-transparent(var(--navbar-color), 0.7)}; --nav-link-icon-width: #{$sidebar-nav-icon-width}; --nav-link-icon-height: #{$sidebar-nav-icon-height}; --sidebar-inset: #{$sidebar-inset}; --sidebar-nav-padding-y: #{$sidebar-nav-padding-y}; --sidebar-nav-padding-x: #{$sidebar-nav-padding-x}; --sidebar-nav-indent: #{$sidebar-nav-indent}; --sidebar-section-title-font-size: #{$sidebar-section-title-font-size}; --sidebar-section-title-margin-top: #{$sidebar-section-title-margin-top}; // Inset the list so the rounded hover/active pills never touch the // sidebar edges. The same inset and link padding apply in the folded // state, so icons keep their exact position when the sidebar folds. padding-inline: var(--sidebar-inset); margin-inline-start: 0; margin-inline-end: 0; .nav-link { justify-content: start; padding: var(--sidebar-nav-padding-y) var(--sidebar-nav-padding-x); // In a vertical list the badge sits at the end of the row. .badge:not(:empty) { margin-inline-start: auto; } } // One line, ellipsized — a title never wraps, so rows keep their height // while the folding sidebar animates its width. .nav-link-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } // stylelint-disable-next-line selector-max-class .nav-item.active > .nav-link { --nav-link-icon-color: currentColor; font-weight: var(--font-weight-medium); background: var(--navbar-active-bg); } // A group that only holds the current page is emphasized, not filled: the // pill marks the page itself, and painting the group with the same pill // reads as two selections. The folded rail puts it back — there the titles // are collapsed and the submenu is a flyout, so the pill is the only thing // left that can point at the group. // stylelint-disable-next-line selector-max-class .nav-item.active > .nav-link.dropdown-toggle { background: transparent; } // Group label — aligned with the item text, muted like the icons. .nav-section-title { display: block; padding: 0.25rem var(--sidebar-nav-padding-x); margin-top: var(--sidebar-section-title-margin-top); font-size: var(--sidebar-section-title-font-size); font-weight: var(--font-weight-medium); color: #{color-transparent(var(--navbar-color), 0.7)}; text-transform: uppercase; letter-spacing: 0.04em; &:first-child { margin-top: 0; } } } // .navbar-side wraps the menu's trailing group (a Customize link, a help // link) in its own .navbar-nav. The base rule lays it out as a row for the // horizontal navbar; here it stacks like the menu, never grows, and the // auto margin pins it to the bottom of the scroll area when the menu is // shorter than the sidebar. .navbar-side { flex-grow: 0; flex-direction: column; align-items: stretch; justify-content: start; padding-bottom: 0.25rem; margin-top: auto; .navbar-nav { flex-grow: 0; } } .navbar-collapse { flex-direction: column; [class^='container'] { flex-direction: column; align-items: stretch; padding: 0; } .dropdown-menu-columns { flex-direction: column; } .dropdown-menu { position: static; min-width: 0; padding: 0; margin: 0; color: inherit; background: transparent; border: none; box-shadow: none; .dropdown-item { display: flex; width: auto; min-width: 0; // Link padding + the icon column — items align with the parent // link's title, whatever the icon size and paddings are set to. padding-inline-start: calc(var(--sidebar-nav-padding-x) + var(--nav-link-icon-width) + var(--nav-link-icon-margin-end)); color: inherit; @include border-radius(var(--border-radius)); &.disabled { color: var(--disabled-color); pointer-events: none; background-color: transparent; } &.active, &:active { font-weight: var(--font-weight-medium); background: var(--navbar-active-bg); } } .dropdown-menu .dropdown-item { padding-inline-start: calc(var(--sidebar-nav-padding-x) + var(--nav-link-icon-width) + var(--nav-link-icon-margin-end) + var(--sidebar-nav-indent)); } // Third-level submenu in the mobile vertical navbar — genuinely nested two // dropdown-menu levels deep. // stylelint-disable-next-line selector-max-class, selector-max-compound-selectors .dropdown-menu .dropdown-menu .dropdown-item { padding-inline-start: calc(var(--sidebar-nav-padding-x) + var(--nav-link-icon-width) + var(--nav-link-icon-margin-end) + 2 * var(--sidebar-nav-indent)); } } .dropdown-toggle::after { margin-inline-start: auto; } // The active pill is the selection indicator — no underline or edge bar. .nav-item.active::after { content: none; } } } // Icon-only rail: hides link titles, centers icons and turns the inline submenus // flattened by navbar-vertical-nav() back into floating flyout dropdowns. @mixin navbar-vertical-folded { // Flyouts must escape the rail, so the folded sidebar itself cannot scroll. overflow: visible; // A wide brand (e.g. a full logo) must not push the container beyond the // rail width; the brand crops instead. [class^='container'] { min-width: 0; } // The brand keeps the expanded state's start padding, so the logo does not // move when the sidebar folds; anything wider than the rail crops. .navbar-brand { overflow: hidden; } // The pin button lives on the expanded sidebar only; the folded rail // unfolds by hovering (folded-hover) or via an outside control. [data-bs-toggle='sidebar-folded'] { display: none; } // The list inset and link padding are identical in both states, so the // icons keep their exact position — only the titles collapse. Aside-wide // (not .navbar-collapse-scoped), so the footer outside the collapse folds // the same way. .navbar-nav .nav-link { --nav-link-icon-margin-end: 0; } // Only icons are readable in the rail, so the group holding the current page // takes the pill back — the emphasis the expanded sidebar carries on the // title alone cannot be seen here. Scoped through .navbar-nav to match the // specificity of the rule it overrides, which wins on source order. // stylelint-disable-next-line selector-max-class .navbar-nav .nav-item.active > .nav-link.dropdown-toggle { background: var(--navbar-active-bg); } // Collapsed, not display: none — titles stay in the accessibility tree. .nav-link-title { width: 0; overflow: hidden; white-space: nowrap; opacity: 0; } .navbar-collapse { // Flyouts must escape the nav's scroll area; the folded rail therefore // cannot scroll (documented — the hover variant is the escape hatch). overflow: visible; // The label text cannot fit the rail — indent it away (stays in the // accessibility tree, line box keeps the element height stable) and show // a short separator instead, centered within the WHOLE gap between the // groups: the box's 50% is shifted up by half the label's top margin. .nav-section-title { position: relative; overflow: hidden; text-indent: -9999px; white-space: nowrap; &::before { position: absolute; inset-inline-start: calc(50% - 0.625rem); top: calc(50% - var(--sidebar-section-title-margin-top) / 2); width: 1.25rem; height: var(--border-width); content: ''; background: #{color-transparent(var(--navbar-color), 0.2)}; } // Nothing above the first group to separate — no line, and no margin // shift to compensate for either. &:first-child::before { content: none; } } .nav-link.dropdown-toggle::after { display: none; } // Restore the multi-column layout that navbar-vertical-nav() stacks — // it keeps long flyout menus from overflowing the viewport. .dropdown-menu-columns { flex-direction: row; } .dropdown-menu { position: absolute; inset-inline-start: 100%; top: 0; min-width: var(--dropdown-min-width); padding: var(--dropdown-padding-y) var(--dropdown-padding-x); margin: 0; color: var(--dropdown-color); background-color: var(--dropdown-bg); background-clip: padding-box; // No border — like the base .dropdown-menu, whose edge is the 1px ring // built into --shadow-dropdown. box-shadow: var(--dropdown-box-shadow); @include border-radius(var(--dropdown-border-radius)); .dropdown-item { padding: var(--dropdown-item-padding-y) var(--dropdown-item-padding-x); color: var(--dropdown-link-color); @include border-radius(0); } // Undo the tree indents navbar-vertical-nav() puts on nested levels. .dropdown-menu .dropdown-item { padding-inline-start: var(--dropdown-item-padding-x); } // stylelint-disable-next-line selector-max-class, selector-max-compound-selectors .dropdown-menu .dropdown-menu .dropdown-item { padding-inline-start: var(--dropdown-item-padding-x); } } // The nav list is inset, so 100% of the .dropdown lands short of the rail // edge — compensate for the inset and add a small gap, so the flyout's // border does not sit flush against the rail's edge border. Nested // (dropend) flyouts are not inset and keep the plain 100%. .nav-item > .dropdown-menu { inset-inline-start: calc(100% + var(--sidebar-inset) + 0.25rem); } } } // Navbar .navbar { --navbar-bg: var(--bg-surface); --navbar-border-width: #{$navbar-border-width}; --navbar-active-border-color: #{$navbar-active-border-color}; --navbar-active-bg: #{$navbar-light-active-bg}; --navbar-border-color: #{$navbar-border-color}; --navbar-hover-color: #{$navbar-hover-color}; align-items: stretch; min-height: $navbar-height; color: var(--navbar-color); background: var(--navbar-bg); box-shadow: inset 0 calc(-1 * var(--navbar-border-width)) 0 0 var(--navbar-border-color); @include media-print() { display: none; } .navbar-collapse & { flex-grow: 1; } &.collapsing { min-height: 0; } .dropdown-menu { position: absolute; z-index: $zindex-fixed; } .navbar-nav { --nav-link-icon-margin-end: #{$nav-link-icon-margin-end}; min-height: subtract($navbar-height, 2 * $navbar-padding-y); // Section labels only make sense in a vertical list — the vertical // navbar styles re-enable them. .nav-section-title { display: none; } .nav-link { position: relative; justify-content: center; min-width: 2.5rem; min-height: 2.5rem; @include border-radius(var(--border-radius)); // A badge with text is part of the link's row, after the title. Only an // empty badge is a notification dot pinned to the link's corner, so the // absolute positioning is limited to `:empty` — a text badge positioned // that way overlapped the title and, in a stacked list, hung past the // link's edge and made the page scroll sideways. .badge { margin-inline-start: $spacer-2; &:empty { position: absolute; inset-inline-end: 0.5rem; top: 0.5rem; margin-inline-start: 0; /* rtl:ignore */ transform: translate(calc(var(--dir) * 50%), -50%); } } } } } // The vertical navbar is a vertical list at every width: the stacked mobile // menu below its breakpoint, the sidebar from it up. Emitted once instead of // inside every breakpoint of both loops; it stays ahead of them in source // order, so the per-breakpoint rules below still override it. @if $enable-navbar-vertical { .navbar-vertical[class*='navbar-expand'] { @include navbar-vertical-nav(); } } .navbar-expand { @each $breakpoint in map.keys($grid-breakpoints) { $next: breakpoint-next($breakpoint, $grid-breakpoints); $infix: breakpoint-infix($next, $grid-breakpoints); &#{$infix} { @if $next { @include media-breakpoint-down($next) { @include navbar-vertical-nav(); // Below the breakpoint the vertical navbar is a top bar row: toggler, // brand, and whatever trails them (a user block, or nothing). Auto // margins keep the brand centered either way — space-between alone // would push it to the far edge when nothing follows it. &.navbar-vertical .navbar-brand { margin-inline: auto; } // In the stacked mobile menu nothing pins the trailing group to the // bottom, so a small gap marks it as a separate part of the menu. &.navbar-vertical .navbar-side { margin-top: $sidebar-inset; } // Below the breakpoint the navbar-footer sits in the top bar row: // the avatar alone, with its menu dropping down instead of up. .navbar-footer { .nav-link-title { display: none; } .dropup .dropdown-menu { inset-inline-start: auto; inset-inline-end: 0; top: 100%; bottom: auto; } } } } @include media-breakpoint-up($next) { .navbar-collapse { flex: 1 1 auto; width: auto; } .nav-item.active { position: relative; .nav-link { color: var(--navbar-active-color); } &::after { position: absolute; inset-inline-start: 0; inset-inline-end: 0; bottom: -0.25rem; content: ''; border: 0 var(--border-style) var(--navbar-active-border-color); border-bottom-width: 2px; } } &.navbar-vertical { box-shadow: inset calc(-1 * var(--navbar-border-width)) 0 0 0 var(--navbar-border-color); &.navbar-right, &.navbar-end { box-shadow: inset calc(1 * var(--navbar-border-width)) 0 0 0 var(--navbar-border-color); } } &.navbar-vertical { ~ .navbar, ~ .page-wrapper { margin-inline-start: calc(var(--sidebar-width) + var(--sidebar-gap, 0px)); } } &.navbar-vertical.navbar-right, &.navbar-vertical.navbar-end { ~ .navbar, ~ .page-wrapper { margin-inline-start: 0; margin-inline-end: calc(var(--sidebar-width) + var(--sidebar-gap, 0px)); } } } // In the first, tightest segment after a horizontal navbar expands there // is no room for icons — hide them there, derived from the navbar's own // navbar-expand-* breakpoint, so the markup needs no display utilities. // The vertical navbar always shows its icons. @if $next and breakpoint-next($next, $grid-breakpoints) { @include media-breakpoint-only($next) { &:not(.navbar-vertical) .navbar-nav .nav-link-icon { display: none; } } } } } } // Navbar brand .navbar-brand { display: inline-flex; gap: $spacer-2; align-items: center; margin: 0; font-weight: $navbar-brand-font-weight; line-height: 1; } .navbar-brand-image { width: auto; height: $navbar-brand-image-height; } .navbar-brand-gray { --navbar-logo-color: var(--tertiary); opacity: 0.5; } // Navbar toggler .navbar-toggler { position: relative; display: flex; align-items: center; justify-content: center; width: $navbar-brand-image-height; height: $navbar-brand-image-height; border: 0; } .navbar-toggler-icon { position: relative; // The button's padding leaves less room than the bar is wide — without // this the middle bar gets flex-squeezed while the pseudo bars keep their // inherited width, and the open-state X loses a leg. flex-shrink: 0; width: 1.25em; height: 2px; background: currentColor; mask-image: none; // the bars below replace the masked hamburger from bootstrap/_navbar @include border-radius(var(--border-radius-pill)); @include transition(top $navbar-toggler-animation-time $navbar-toggler-animation-time, bottom $navbar-toggler-animation-time $navbar-toggler-animation-time, transform $navbar-toggler-animation-time, opacity 0s $navbar-toggler-animation-time); &::before, &::after { position: absolute; inset-inline-start: 0; display: block; width: inherit; height: inherit; content: ''; background: inherit; border-radius: inherit; @include transition(inherit); } &::before { top: -0.45em; } &::after { bottom: -0.45em; } .navbar-toggler[aria-expanded='true'] & { transform: rotate(45deg); @include transition(top $transition-time, bottom $transition-time, transform $transition-time $transition-time, opacity 0s $transition-time); &::before { top: 0; transform: rotate(-90deg); } &::after { bottom: 0; opacity: 0; } } } // Navbar transparent .navbar-transparent { --navbar-border-color: transparent !important; background: transparent !important; } // Navbar nav .navbar-nav { --nav-link-hover-bg: #{$navbar-nav-link-hover-bg}; --nav-link-icon-color: #{$nav-link-icon-color}; --nav-link-hover-icon-color: #{$nav-link-hover-icon-color}; align-items: stretch; padding: 0; margin: 0; .nav-item { display: flex; flex-direction: column; justify-content: center; } } // Navbar side // The group at the end of a navbar: a row on the end side of a horizontal // navbar. In a vertical navbar the same class pins the group to the bottom // of the menu — see navbar-vertical-nav(). .navbar-side { display: flex; flex-direction: row; align-items: center; justify-content: space-around; margin: 0; } // Navbar sticky // Runtime route only (data-bs-navbar="sticky" on ); static markup wraps // the navbar in its own .sticky-top. Just the top bar sticks — on the two-row // navbar the menu row scrolls away, so the sticky strip stays one row high. html[data-bs-navbar='sticky'] .page > .navbar:not(.navbar-vertical) { position: sticky; top: 0; z-index: $zindex-sticky; } // Navbar primary // Runtime route only (data-bs-navbar-theme="primary" on ): the primary // color as the navbar surface. The text palette comes from the dark color // mode, which _extends.scss applies to the same selector; static markup gets // the same look from .bg-primary with data-bs-theme="dark" on the navbar. html[data-bs-navbar-theme='primary'] .page > .navbar { --navbar-bg: var(--primary); --navbar-border-color: transparent; } // The menu row of the two-row navbar, which is not a direct child of .page. html[data-bs-navbar-theme='primary'] .page > [class*='navbar-expand'] .navbar { --navbar-bg: var(--primary); --navbar-border-color: transparent; } // Navbar vertical @if $enable-navbar-vertical { // Folded sidebar: every geometry consumer (aside width, page paddings, // page-wrapper/navbar margins) reads --sidebar-width, so flipping this one // variable re-lays-out the whole page. Two routes to the same state: // a .navbar-folded/.navbar-folded-hover class on the aside (static markup), // or data-bs-sidebar="folded"/"folded-hover" on (set pre-render from // localStorage by tabler-theme.js, toggled by [data-bs-toggle="sidebar-folded"]). // html[…] beats the :root declaration of --sidebar-width on the same element; // a bare :where()/[…] selector would lose to it and never take effect. html[data-bs-sidebar^='folded'] { --sidebar-width: var(--sidebar-folded-width); // The sidebar is rendered for the expanded state, so the active submenu can // arrive open (.show). Folded, it would paint as a flyout until sidebar.ts // closes it on DOMContentLoaded — a flicker on every reload. Menus opened by // the Dropdown plugin carry a data-bs-popper attribute, so only the // server-rendered ones hide. .navbar-vertical .dropdown-menu.show:not([data-bs-popper]):not([data-tblr-popper]) { display: none; } } .navbar-vertical:is(.navbar-folded, .navbar-folded-hover) { --sidebar-width: var(--sidebar-folded-width); // The class sits on the aside, so its layout siblings need their own copy. ~ .page, ~ .page-wrapper, ~ .navbar { --sidebar-width: var(--sidebar-folded-width); } } // Navbar position (data-bs-navbar-position on ). A page can render both // navbars — the vertical aside first, the horizontal navbar(s) after — and // these rules show one of them. Horizontal is the default and writes no // attribute, so its rule matches on the attribute's absence. // // Both rules are guarded with :has() so they only fire on a page that really // renders the other navbar: a page with a single navbar keeps it, whatever a // stale stored choice says, and can never end up with no navigation at all. html:not([data-bs-navbar-position='vertical']) .page:has(> [class*='navbar-expand']:not(.navbar-vertical)) { > .navbar-vertical { display: none; } // The hidden aside must leave no gap: its siblings offset themselves by // --sidebar-width, and this declaration sits on the same elements as the // folded copy above, so it wins by coming later. // A length, not a bare 0: the offset is added up in calc(). A floating // navbar keeps its own gap from the viewport edge. > .navbar, > .page-wrapper { --sidebar-width: 0px; --sidebar-gap: var(--navbar-margin, 0px); } } html[data-bs-navbar-position='vertical'] .page:has(> .navbar-vertical) { > [class*='navbar-expand']:not(.navbar-vertical) { display: none; } } .navbar-vertical { &.navbar-expand { @each $breakpoint in map.keys($grid-breakpoints) { $next: breakpoint-next($breakpoint, $grid-breakpoints); $infix: breakpoint-infix($next, $grid-breakpoints); &#{$infix} { @include media-breakpoint-up($next) { position: fixed; inset-inline-start: 0; top: 0; bottom: 0; z-index: $zindex-fixed; align-items: start; width: var(--sidebar-width); padding: 0; @include transition(transform $transition-time); &.navbar-right, &.navbar-end { inset-inline-start: auto; inset-inline-end: 0; } // Logo on the start side, room for an action (e.g. the pin // toggle) on the end side. The inline padding centers the logo // mark in the folded rail and is identical in both states, so the // logo mark and the nav icons share one center axis and nothing // moves when the sidebar folds. .navbar-brand { --sidebar-brand-padding-x: #{$sidebar-brand-padding-x}; justify-content: space-between; padding: (($navbar-height - $navbar-brand-image-height) * 0.5) var(--sidebar-brand-padding-x); margin-inline-end: 0; } // The fold toggle keeps its layout slot (no jumping) but only // becomes visible while the sidebar is hovered or focused. [data-bs-toggle='sidebar-folded'] { opacity: 0; @include transition(opacity $transition-time); } &:hover [data-bs-toggle='sidebar-folded'], &:has(:focus-visible) [data-bs-toggle='sidebar-folded'] { opacity: 1; } // The nav is the sidebar's scroll area — the brand above and the // footer below stay pinned. .navbar-collapse { align-items: stretch; min-height: 0; overflow-y: auto; } .navbar-nav { flex-grow: 1; flex-direction: column; min-height: auto; } // Bottom-pinned footer area, e.g. a user block. Sits after the // growing nav, separated with the navbar border. .navbar-footer { // In the markup the footer sits before the collapse (so it // stays in the mobile top bar row); order moves it to the // bottom of the desktop column. order: 1; padding-block: $sidebar-inset; margin-top: auto; border-top: var(--navbar-border-width) var(--border-style) var(--navbar-border-color); // Tighter link padding than the menu — the avatar brings its // own bulk. Overrides the variables the nav-link padding reads, // so it wins regardless of rule order. .navbar-nav { --sidebar-nav-padding-y: 0.5rem; --sidebar-nav-padding-x: 0.5rem; flex-grow: 0; } .nav-link .avatar { // Never squeezed by the appearing title during the hover // unfold animation. flex-shrink: 0; margin-inline-end: var(--nav-link-icon-margin-end); } } > [class^='container'] { flex-direction: column; align-items: stretch; justify-content: start; height: 100%; padding: 0; } ~ .page { padding-inline-start: var(--sidebar-width); [class^='container'] { padding-inline-start: 1.5rem; padding-inline-end: 1.5rem; } } &.navbar-right ~ .page, &.navbar-end ~ .page { padding-inline-start: 0; padding-inline-end: var(--sidebar-width); } // Folded state, both routes; the hover variant folds only while // not hovered and not holding visible keyboard focus. Plain // :focus-within would keep the sidebar stuck open after a mouse // click, because the clicked link keeps focus. // One :is() instead of a selector list — the mixin's rules are long // and a list would repeat each of them three times. &:is(.navbar-folded, .navbar-folded-hover:not(:hover):not(:has(:focus-visible)), [data-bs-sidebar='folded'] *, [data-bs-sidebar='folded-hover'] :not(:hover):not(:has(:focus-visible))) { @include navbar-vertical-folded(); } &.navbar-folded-hover, :where([data-bs-sidebar='folded-hover']) & { @include transition(transform $transition-time, width $transition-time); .nav-link-title { @include transition(opacity $transition-time); } // Temporarily unfolds as an overlay: siblings keep the folded // --sidebar-width, so page content does not reflow. &:hover, &:has(:focus-visible) { width: $sidebar-width; box-shadow: inset calc(-1 * var(--navbar-border-width)) 0 0 0 var(--navbar-border-color), var(--shadow-lg); } } } } } } } @include media-print() { .navbar-vertical ~ .page { padding-inline-start: 0; padding-inline-end: 0; } } } .navbar-overlap { &::after { position: absolute; inset-inline-start: 0; inset-inline-end: 0; top: 100%; z-index: -1; height: $navbar-overlap-height; content: ''; background: inherit; box-shadow: inherit; } } // Navbar floating // The navbar keeps a gap from the edges around it, like .offcanvas-floating, // with an outline on every side instead of the single border line. // // Two routes to the same state: the .navbar-floating class on the navbar // (static markup), or data-bs-navbar-style="floating" on (the theme // settings), which covers whichever navigation the page shows — the menu row // of the two-row navbar included, which inherits the variables from its // navbar-expand-* wrapper. // // The variables exist only from the navbar-expand-* breakpoint up, so below it // every fallback applies and the navbar stays attached. $navbar-floating-attr: "html[data-bs-navbar-style='floating'] .page >"; .navbar-floating, #{$navbar-floating-attr} .navbar, #{$navbar-floating-attr} [class*='navbar-expand'] .navbar { margin: var(--navbar-margin, 0); outline: var(--navbar-outline-width, 0) solid var(--navbar-border-color); outline-offset: calc(-1 * var(--navbar-outline-width, 0px)); @include border-radius(var(--navbar-border-radius, 0)); } #{$navbar-floating-attr} [class*='navbar-expand'] .navbar { margin-top: 0; } @each $breakpoint in map.keys($grid-breakpoints) { $next: breakpoint-next($breakpoint, $grid-breakpoints); $infix: breakpoint-infix($next, $grid-breakpoints); @include media-breakpoint-up($next) { .navbar-floating.navbar-expand#{$infix}, #{$navbar-floating-attr} .navbar-expand#{$infix} { --navbar-margin: #{$navbar-floating-margin}; --navbar-border-radius: #{$navbar-floating-border-radius}; --navbar-outline-width: var(--navbar-border-width); } } } // The page next to a floating sidebar moves over by the sidebar's two margins, // and a floating navbar next to any sidebar keeps its own gap from it. The // sibling offsets read --sidebar-gap only from the sidebar's breakpoint up. .navbar-vertical ~ .navbar-floating { --sidebar-gap: var(--navbar-margin, 0px); } .navbar-floating.navbar-vertical ~ :is(.navbar, .page-wrapper), #{$navbar-floating-attr} .navbar-vertical ~ :is(.navbar, .page-wrapper) { --sidebar-gap: calc(2 * #{$navbar-floating-margin}); }