28 CSS Hamburger Menus16 / 28
Bootstrap 5 Navbar Hamburger Menu Toggle Setup
The canonical Bootstrap 5.3 navbar, assembled correctly on the first try: navbar-expand-lg picks the breakpoint, navbar-toggler + data-bs-toggle wire the collapse plugin with zero custom JS, and every class in the chain is annotated so you know which ones are load-bearing. Includes the three fixes for 'bootstrap hamburger not working' (the #1 support thread for this pattern) and the CSS to replace the default toggler icon with an animated one.
Published Updated
The code
<section class="chm-16" aria-label="Bootstrap 5 navbar hamburger setup demo">
<div class="chm-16__stage">
<div class="chm-16__frame">
<nav class="navbar" aria-label="Main">
<div class="container-fluid">
<a class="navbar-brand" href="#home"><span class="brand-dot" aria-hidden="true"></span>Bootlace</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#chm16-nav" aria-controls="chm16-nav" aria-expanded="false" aria-label="Toggle navigation">
<span class="chm-16__bars" aria-hidden="true"><i></i><i></i><i></i></span>
</button>
<div class="collapse navbar-collapse" id="chm16-nav">
<ul class="navbar-nav">
<li class="nav-item"><a class="nav-link active" aria-current="page" href="#home">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#features">Features</a></li>
<li class="nav-item"><a class="nav-link" href="#pricing">Pricing</a></li>
<li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Beta</a></li>
</ul>
</div>
</div>
</nav>
<div class="chm-16__page" aria-hidden="true"><span style="--w:76%"></span><span style="--w:90%"></span><span style="--w:58%"></span></div>
</div>
<p class="chm-16__note" aria-hidden="true">card is narrower than the <code>lg</code> breakpoint, so the toggler shows. The shim stands in for <code>bootstrap.bundle.min.js</code> here — load the real bundle in your project.</p>
</div>
</section><section class="chm-16" aria-label="Bootstrap 5 navbar hamburger setup demo">
<div class="chm-16__stage">
<div class="chm-16__frame">
<nav class="navbar" aria-label="Main">
<div class="container-fluid">
<a class="navbar-brand" href="#home"><span class="brand-dot" aria-hidden="true"></span>Bootlace</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#chm16-nav" aria-controls="chm16-nav" aria-expanded="false" aria-label="Toggle navigation">
<span class="chm-16__bars" aria-hidden="true"><i></i><i></i><i></i></span>
</button>
<div class="collapse navbar-collapse" id="chm16-nav">
<ul class="navbar-nav">
<li class="nav-item"><a class="nav-link active" aria-current="page" href="#home">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#features">Features</a></li>
<li class="nav-item"><a class="nav-link" href="#pricing">Pricing</a></li>
<li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Beta</a></li>
</ul>
</div>
</div>
</nav>
<div class="chm-16__page" aria-hidden="true"><span style="--w:76%"></span><span style="--w:90%"></span><span style="--w:58%"></span></div>
</div>
<p class="chm-16__note" aria-hidden="true">card is narrower than the <code>lg</code> breakpoint, so the toggler shows. The shim stands in for <code>bootstrap.bundle.min.js</code> here — load the real bundle in your project.</p>
</div>
</section>.chm-16,
.chm-16 *,
.chm-16 *::before,
.chm-16 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.chm-16 {
background: var(--bg);
width: 100%;
min-height: 100vh;
box-sizing: border-box;
--body: oklch(1 0 0);
--tertiary: oklch(0.97 0.004 260);
--border: oklch(0.9 0.007 260);
--ink: oklch(0.28 0.02 262);
--mut: oklch(0.5 0.016 262);
--primary: oklch(0.55 0.19 262);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
}
.chm-16__stage {
border: 1px solid var(--border);
border-radius: 18px;
background: oklch(0.985 0.003 260);
padding: 20px;
display: grid;
gap: 14px;
}
.chm-16__frame {
container-type: inline-size;
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
background: var(--body);
box-shadow: 0 14px 34px -26px oklch(0.3 0.03 262/.6);
}
.chm-16 .navbar {
background: var(--tertiary);
border-bottom: 1px solid var(--border);
padding: 8px 0;
}
.chm-16 .container-fluid {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 0 16px;
}
.chm-16 .navbar-brand {
display: flex;
align-items: center;
gap: 9px;
font-size: 16.5px;
font-weight: 600;
color: var(--ink);
text-decoration: none;
padding: 6px 0;
}
.chm-16 .brand-dot {
width: 19px;
height: 19px;
border-radius: 5px;
background: linear-gradient(140deg,var(--primary),oklch(0.65 0.2 300));
}
.chm-16 .navbar-toggler {
display: grid;
place-items: center;
width: 52px;
height: 44px;
border: 1px solid oklch(0.82 0.012 262);
border-radius: 8px;
background: transparent;
color: var(--ink);
cursor: pointer;
transition: box-shadow .15s;
}
.chm-16 .navbar-toggler:focus-visible {
outline: none;
box-shadow: 0 0 0 3px color-mix(in oklch,var(--primary) 35%,transparent);
}
.chm-16__bars {
position: relative;
width: 20px;
height: 14px;
display: block;
}
.chm-16__bars i {
position: absolute;
left: 0;
width: 20px;
height: 2px;
border-radius: 2px;
background: currentColor;
transition: translate .28s,rotate .28s .04s,opacity .2s;
}
.chm-16__bars i:nth-child(1) {
top: 0;
}
.chm-16__bars i:nth-child(2) {
top: 6px;
}
.chm-16__bars i:nth-child(3) {
top: 12px;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(1) {
translate: 0 6px;
rotate: 45deg;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(2) {
opacity: 0;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(3) {
translate: 0 -6px;
rotate: -45deg;
}
.chm-16 .navbar-collapse {
flex-basis: 100%;
overflow: hidden;
}
.chm-16 .navbar-collapse.collapse:not(.show) {
display: none;
}
.chm-16 .navbar-collapse.collapsing {
height: 0;
transition: height .3s ease;
}
.chm-16 .navbar-nav {
list-style: none;
display: flex;
flex-direction: column;
padding: 6px 4px 10px;
}
.chm-16 .nav-link {
display: block;
padding: 10px 12px;
border-radius: 8px;
font-size: 14.5px;
font-weight: 500;
color: var(--mut);
text-decoration: none;
transition: color .15s,background .15s;
}
.chm-16 .nav-link:hover,
.chm-16 .nav-link:focus-visible {
color: var(--ink);
background: oklch(0.94 0.006 260);
}
.chm-16 .nav-link:focus-visible {
outline: none;
box-shadow: 0 0 0 3px color-mix(in oklch,var(--primary) 35%,transparent);
}
.chm-16 .nav-link.active {
color: var(--ink);
font-weight: 600;
}
.chm-16 .nav-link.disabled {
color: oklch(0.72 0.008 262);
pointer-events: none;
}
@container (min-width: 768px) {
.chm-16 .navbar-toggler {
display: none;
}
.chm-16 .navbar-collapse {
flex-basis: auto;
}
.chm-16 .navbar-collapse.collapse:not(.show) {
display: flex !important;
}
.chm-16 .navbar-nav {
flex-direction: row;
gap: 2px;
padding: 0;
margin-left: auto;
}
}
.chm-16__page {
padding: 16px;
display: grid;
gap: 11px;
}
.chm-16__page span {
display: block;
height: 10px;
width: var(--w);
border-radius: 99px;
background: oklch(0.94 0.004 260);
}
.chm-16__note {
font-size: 12px;
line-height: 1.6;
color: var(--mut);
text-wrap: pretty;
}
.chm-16__note code {
font: 11px ui-monospace,Menlo,Consolas,monospace;
background: oklch(0.93 0.005 260);
border-radius: 5px;
padding: 1.5px 6px;
}
@media (prefers-reduced-motion: reduce) {
.chm-16 * {
transition-duration: .01s !important;
}
}.chm-16,
.chm-16 *,
.chm-16 *::before,
.chm-16 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.chm-16 {
background: var(--bg);
width: 100%;
min-height: 100vh;
box-sizing: border-box;
--body: oklch(1 0 0);
--tertiary: oklch(0.97 0.004 260);
--border: oklch(0.9 0.007 260);
--ink: oklch(0.28 0.02 262);
--mut: oklch(0.5 0.016 262);
--primary: oklch(0.55 0.19 262);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
}
.chm-16__stage {
border: 1px solid var(--border);
border-radius: 18px;
background: oklch(0.985 0.003 260);
padding: 20px;
display: grid;
gap: 14px;
}
.chm-16__frame {
container-type: inline-size;
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
background: var(--body);
box-shadow: 0 14px 34px -26px oklch(0.3 0.03 262/.6);
}
.chm-16 .navbar {
background: var(--tertiary);
border-bottom: 1px solid var(--border);
padding: 8px 0;
}
.chm-16 .container-fluid {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 0 16px;
}
.chm-16 .navbar-brand {
display: flex;
align-items: center;
gap: 9px;
font-size: 16.5px;
font-weight: 600;
color: var(--ink);
text-decoration: none;
padding: 6px 0;
}
.chm-16 .brand-dot {
width: 19px;
height: 19px;
border-radius: 5px;
background: linear-gradient(140deg,var(--primary),oklch(0.65 0.2 300));
}
.chm-16 .navbar-toggler {
display: grid;
place-items: center;
width: 52px;
height: 44px;
border: 1px solid oklch(0.82 0.012 262);
border-radius: 8px;
background: transparent;
color: var(--ink);
cursor: pointer;
transition: box-shadow .15s;
}
.chm-16 .navbar-toggler:focus-visible {
outline: none;
box-shadow: 0 0 0 3px color-mix(in oklch,var(--primary) 35%,transparent);
}
.chm-16__bars {
position: relative;
width: 20px;
height: 14px;
display: block;
}
.chm-16__bars i {
position: absolute;
left: 0;
width: 20px;
height: 2px;
border-radius: 2px;
background: currentColor;
transition: translate .28s,rotate .28s .04s,opacity .2s;
}
.chm-16__bars i:nth-child(1) {
top: 0;
}
.chm-16__bars i:nth-child(2) {
top: 6px;
}
.chm-16__bars i:nth-child(3) {
top: 12px;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(1) {
translate: 0 6px;
rotate: 45deg;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(2) {
opacity: 0;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(3) {
translate: 0 -6px;
rotate: -45deg;
}
.chm-16 .navbar-collapse {
flex-basis: 100%;
overflow: hidden;
}
.chm-16 .navbar-collapse.collapse:not(.show) {
display: none;
}
.chm-16 .navbar-collapse.collapsing {
height: 0;
transition: height .3s ease;
}
.chm-16 .navbar-nav {
list-style: none;
display: flex;
flex-direction: column;
padding: 6px 4px 10px;
}
.chm-16 .nav-link {
display: block;
padding: 10px 12px;
border-radius: 8px;
font-size: 14.5px;
font-weight: 500;
color: var(--mut);
text-decoration: none;
transition: color .15s,background .15s;
}
.chm-16 .nav-link:hover,
.chm-16 .nav-link:focus-visible {
color: var(--ink);
background: oklch(0.94 0.006 260);
}
.chm-16 .nav-link:focus-visible {
outline: none;
box-shadow: 0 0 0 3px color-mix(in oklch,var(--primary) 35%,transparent);
}
.chm-16 .nav-link.active {
color: var(--ink);
font-weight: 600;
}
.chm-16 .nav-link.disabled {
color: oklch(0.72 0.008 262);
pointer-events: none;
}
@container (min-width: 768px) {
.chm-16 .navbar-toggler {
display: none;
}
.chm-16 .navbar-collapse {
flex-basis: auto;
}
.chm-16 .navbar-collapse.collapse:not(.show) {
display: flex !important;
}
.chm-16 .navbar-nav {
flex-direction: row;
gap: 2px;
padding: 0;
margin-left: auto;
}
}
.chm-16__page {
padding: 16px;
display: grid;
gap: 11px;
}
.chm-16__page span {
display: block;
height: 10px;
width: var(--w);
border-radius: 99px;
background: oklch(0.94 0.004 260);
}
.chm-16__note {
font-size: 12px;
line-height: 1.6;
color: var(--mut);
text-wrap: pretty;
}
.chm-16__note code {
font: 11px ui-monospace,Menlo,Consolas,monospace;
background: oklch(0.93 0.005 260);
border-radius: 5px;
padding: 1.5px 6px;
}
@media (prefers-reduced-motion: reduce) {
.chm-16 * {
transition-duration: .01s !important;
}
}(function () {
/* Stand-in for bootstrap.bundle.min.js collapse — same class dance, same timing. */
document.querySelectorAll('.chm-16__frame').forEach(function (frame) {
if (frame.dataset.bound) return; frame.dataset.bound = '1';
var btn = frame.querySelector('[data-bs-toggle="collapse"]'),
panel = frame.querySelector(btn.getAttribute('data-bs-target'));
btn.addEventListener('click', function () {
var opening = !panel.classList.contains('show');
btn.setAttribute('aria-expanded', String(opening));
panel.classList.remove('collapse');
panel.classList.add('collapsing');
panel.style.height = opening ? '0' : panel.scrollHeight + 'px';
requestAnimationFrame(function () {
panel.style.height = opening ? panel.scrollHeight + 'px' : '0';
});
setTimeout(function () {
panel.classList.remove('collapsing');
panel.classList.add('collapse');
panel.classList.toggle('show', opening);
panel.style.height = '';
}, 300);
});
});
})();(function () {
/* Stand-in for bootstrap.bundle.min.js collapse — same class dance, same timing. */
document.querySelectorAll('.chm-16__frame').forEach(function (frame) {
if (frame.dataset.bound) return; frame.dataset.bound = '1';
var btn = frame.querySelector('[data-bs-toggle="collapse"]'),
panel = frame.querySelector(btn.getAttribute('data-bs-target'));
btn.addEventListener('click', function () {
var opening = !panel.classList.contains('show');
btn.setAttribute('aria-expanded', String(opening));
panel.classList.remove('collapse');
panel.classList.add('collapsing');
panel.style.height = opening ? '0' : panel.scrollHeight + 'px';
requestAnimationFrame(function () {
panel.style.height = opening ? panel.scrollHeight + 'px' : '0';
});
setTimeout(function () {
panel.classList.remove('collapsing');
panel.classList.add('collapse');
panel.classList.toggle('show', opening);
panel.style.height = '';
}, 300);
});
});
})();Here's a working CSS Hamburger Menu from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Bootstrap 5 Navbar Hamburger Menu Toggle Setup
Source: https://codefronts.com/navigation/css-hamburger-menus/bootstrap-5-responsive-navbar-with-hamburger/
The canonical Bootstrap 5.3 navbar, assembled correctly on the first try: navbar-expand-lg picks the breakpoint, navbar-toggler + data-bs-toggle wire the collapse plugin with zero custom JS, and every class in the chain is annotated so you know which ones are load-bearing. Includes the three fixes for 'bootstrap hamburger not working' (the #1 support thread for this pattern) and the CSS to replace the default toggler icon with an animated one.
## HTML
```html
<section class="chm-16" aria-label="Bootstrap 5 navbar hamburger setup demo">
<div class="chm-16__stage">
<div class="chm-16__frame">
<nav class="navbar" aria-label="Main">
<div class="container-fluid">
<a class="navbar-brand" href="#home"><span class="brand-dot" aria-hidden="true"></span>Bootlace</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#chm16-nav" aria-controls="chm16-nav" aria-expanded="false" aria-label="Toggle navigation">
<span class="chm-16__bars" aria-hidden="true"><i></i><i></i><i></i></span>
</button>
<div class="collapse navbar-collapse" id="chm16-nav">
<ul class="navbar-nav">
<li class="nav-item"><a class="nav-link active" aria-current="page" href="#home">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#features">Features</a></li>
<li class="nav-item"><a class="nav-link" href="#pricing">Pricing</a></li>
<li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Beta</a></li>
</ul>
</div>
</div>
</nav>
<div class="chm-16__page" aria-hidden="true"><span style="--w:76%"></span><span style="--w:90%"></span><span style="--w:58%"></span></div>
</div>
<p class="chm-16__note" aria-hidden="true">card is narrower than the <code>lg</code> breakpoint, so the toggler shows. The shim stands in for <code>bootstrap.bundle.min.js</code> here — load the real bundle in your project.</p>
</div>
</section>
```
## CSS
```css
.chm-16,
.chm-16 *,
.chm-16 *::before,
.chm-16 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.chm-16 {
background: var(--bg);
width: 100%;
min-height: 100vh;
box-sizing: border-box;
--body: oklch(1 0 0);
--tertiary: oklch(0.97 0.004 260);
--border: oklch(0.9 0.007 260);
--ink: oklch(0.28 0.02 262);
--mut: oklch(0.5 0.016 262);
--primary: oklch(0.55 0.19 262);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
}
.chm-16__stage {
border: 1px solid var(--border);
border-radius: 18px;
background: oklch(0.985 0.003 260);
padding: 20px;
display: grid;
gap: 14px;
}
.chm-16__frame {
container-type: inline-size;
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
background: var(--body);
box-shadow: 0 14px 34px -26px oklch(0.3 0.03 262/.6);
}
.chm-16 .navbar {
background: var(--tertiary);
border-bottom: 1px solid var(--border);
padding: 8px 0;
}
.chm-16 .container-fluid {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 0 16px;
}
.chm-16 .navbar-brand {
display: flex;
align-items: center;
gap: 9px;
font-size: 16.5px;
font-weight: 600;
color: var(--ink);
text-decoration: none;
padding: 6px 0;
}
.chm-16 .brand-dot {
width: 19px;
height: 19px;
border-radius: 5px;
background: linear-gradient(140deg,var(--primary),oklch(0.65 0.2 300));
}
.chm-16 .navbar-toggler {
display: grid;
place-items: center;
width: 52px;
height: 44px;
border: 1px solid oklch(0.82 0.012 262);
border-radius: 8px;
background: transparent;
color: var(--ink);
cursor: pointer;
transition: box-shadow .15s;
}
.chm-16 .navbar-toggler:focus-visible {
outline: none;
box-shadow: 0 0 0 3px color-mix(in oklch,var(--primary) 35%,transparent);
}
.chm-16__bars {
position: relative;
width: 20px;
height: 14px;
display: block;
}
.chm-16__bars i {
position: absolute;
left: 0;
width: 20px;
height: 2px;
border-radius: 2px;
background: currentColor;
transition: translate .28s,rotate .28s .04s,opacity .2s;
}
.chm-16__bars i:nth-child(1) {
top: 0;
}
.chm-16__bars i:nth-child(2) {
top: 6px;
}
.chm-16__bars i:nth-child(3) {
top: 12px;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(1) {
translate: 0 6px;
rotate: 45deg;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(2) {
opacity: 0;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(3) {
translate: 0 -6px;
rotate: -45deg;
}
.chm-16 .navbar-collapse {
flex-basis: 100%;
overflow: hidden;
}
.chm-16 .navbar-collapse.collapse:not(.show) {
display: none;
}
.chm-16 .navbar-collapse.collapsing {
height: 0;
transition: height .3s ease;
}
.chm-16 .navbar-nav {
list-style: none;
display: flex;
flex-direction: column;
padding: 6px 4px 10px;
}
.chm-16 .nav-link {
display: block;
padding: 10px 12px;
border-radius: 8px;
font-size: 14.5px;
font-weight: 500;
color: var(--mut);
text-decoration: none;
transition: color .15s,background .15s;
}
.chm-16 .nav-link:hover,
.chm-16 .nav-link:focus-visible {
color: var(--ink);
background: oklch(0.94 0.006 260);
}
.chm-16 .nav-link:focus-visible {
outline: none;
box-shadow: 0 0 0 3px color-mix(in oklch,var(--primary) 35%,transparent);
}
.chm-16 .nav-link.active {
color: var(--ink);
font-weight: 600;
}
.chm-16 .nav-link.disabled {
color: oklch(0.72 0.008 262);
pointer-events: none;
}
@container (min-width: 768px) {
.chm-16 .navbar-toggler {
display: none;
}
.chm-16 .navbar-collapse {
flex-basis: auto;
}
.chm-16 .navbar-collapse.collapse:not(.show) {
display: flex !important;
}
.chm-16 .navbar-nav {
flex-direction: row;
gap: 2px;
padding: 0;
margin-left: auto;
}
}
.chm-16__page {
padding: 16px;
display: grid;
gap: 11px;
}
.chm-16__page span {
display: block;
height: 10px;
width: var(--w);
border-radius: 99px;
background: oklch(0.94 0.004 260);
}
.chm-16__note {
font-size: 12px;
line-height: 1.6;
color: var(--mut);
text-wrap: pretty;
}
.chm-16__note code {
font: 11px ui-monospace,Menlo,Consolas,monospace;
background: oklch(0.93 0.005 260);
border-radius: 5px;
padding: 1.5px 6px;
}
@media (prefers-reduced-motion: reduce) {
.chm-16 * {
transition-duration: .01s !important;
}
}
```
## JavaScript
```js
(function () {
/* Stand-in for bootstrap.bundle.min.js collapse — same class dance, same timing. */
document.querySelectorAll('.chm-16__frame').forEach(function (frame) {
if (frame.dataset.bound) return; frame.dataset.bound = '1';
var btn = frame.querySelector('[data-bs-toggle="collapse"]'),
panel = frame.querySelector(btn.getAttribute('data-bs-target'));
btn.addEventListener('click', function () {
var opening = !panel.classList.contains('show');
btn.setAttribute('aria-expanded', String(opening));
panel.classList.remove('collapse');
panel.classList.add('collapsing');
panel.style.height = opening ? '0' : panel.scrollHeight + 'px';
requestAnimationFrame(function () {
panel.style.height = opening ? panel.scrollHeight + 'px' : '0';
});
setTimeout(function () {
panel.classList.remove('collapsing');
panel.classList.add('collapse');
panel.classList.toggle('show', opening);
panel.style.height = '';
}, 300);
});
});
})();
```Here's a working CSS Hamburger Menu from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Bootstrap 5 Navbar Hamburger Menu Toggle Setup
Source: https://codefronts.com/navigation/css-hamburger-menus/bootstrap-5-responsive-navbar-with-hamburger/
The canonical Bootstrap 5.3 navbar, assembled correctly on the first try: navbar-expand-lg picks the breakpoint, navbar-toggler + data-bs-toggle wire the collapse plugin with zero custom JS, and every class in the chain is annotated so you know which ones are load-bearing. Includes the three fixes for 'bootstrap hamburger not working' (the #1 support thread for this pattern) and the CSS to replace the default toggler icon with an animated one.
## HTML
```html
<section class="chm-16" aria-label="Bootstrap 5 navbar hamburger setup demo">
<div class="chm-16__stage">
<div class="chm-16__frame">
<nav class="navbar" aria-label="Main">
<div class="container-fluid">
<a class="navbar-brand" href="#home"><span class="brand-dot" aria-hidden="true"></span>Bootlace</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#chm16-nav" aria-controls="chm16-nav" aria-expanded="false" aria-label="Toggle navigation">
<span class="chm-16__bars" aria-hidden="true"><i></i><i></i><i></i></span>
</button>
<div class="collapse navbar-collapse" id="chm16-nav">
<ul class="navbar-nav">
<li class="nav-item"><a class="nav-link active" aria-current="page" href="#home">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#features">Features</a></li>
<li class="nav-item"><a class="nav-link" href="#pricing">Pricing</a></li>
<li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Beta</a></li>
</ul>
</div>
</div>
</nav>
<div class="chm-16__page" aria-hidden="true"><span style="--w:76%"></span><span style="--w:90%"></span><span style="--w:58%"></span></div>
</div>
<p class="chm-16__note" aria-hidden="true">card is narrower than the <code>lg</code> breakpoint, so the toggler shows. The shim stands in for <code>bootstrap.bundle.min.js</code> here — load the real bundle in your project.</p>
</div>
</section>
```
## CSS
```css
.chm-16,
.chm-16 *,
.chm-16 *::before,
.chm-16 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.chm-16 {
background: var(--bg);
width: 100%;
min-height: 100vh;
box-sizing: border-box;
--body: oklch(1 0 0);
--tertiary: oklch(0.97 0.004 260);
--border: oklch(0.9 0.007 260);
--ink: oklch(0.28 0.02 262);
--mut: oklch(0.5 0.016 262);
--primary: oklch(0.55 0.19 262);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
}
.chm-16__stage {
border: 1px solid var(--border);
border-radius: 18px;
background: oklch(0.985 0.003 260);
padding: 20px;
display: grid;
gap: 14px;
}
.chm-16__frame {
container-type: inline-size;
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
background: var(--body);
box-shadow: 0 14px 34px -26px oklch(0.3 0.03 262/.6);
}
.chm-16 .navbar {
background: var(--tertiary);
border-bottom: 1px solid var(--border);
padding: 8px 0;
}
.chm-16 .container-fluid {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 0 16px;
}
.chm-16 .navbar-brand {
display: flex;
align-items: center;
gap: 9px;
font-size: 16.5px;
font-weight: 600;
color: var(--ink);
text-decoration: none;
padding: 6px 0;
}
.chm-16 .brand-dot {
width: 19px;
height: 19px;
border-radius: 5px;
background: linear-gradient(140deg,var(--primary),oklch(0.65 0.2 300));
}
.chm-16 .navbar-toggler {
display: grid;
place-items: center;
width: 52px;
height: 44px;
border: 1px solid oklch(0.82 0.012 262);
border-radius: 8px;
background: transparent;
color: var(--ink);
cursor: pointer;
transition: box-shadow .15s;
}
.chm-16 .navbar-toggler:focus-visible {
outline: none;
box-shadow: 0 0 0 3px color-mix(in oklch,var(--primary) 35%,transparent);
}
.chm-16__bars {
position: relative;
width: 20px;
height: 14px;
display: block;
}
.chm-16__bars i {
position: absolute;
left: 0;
width: 20px;
height: 2px;
border-radius: 2px;
background: currentColor;
transition: translate .28s,rotate .28s .04s,opacity .2s;
}
.chm-16__bars i:nth-child(1) {
top: 0;
}
.chm-16__bars i:nth-child(2) {
top: 6px;
}
.chm-16__bars i:nth-child(3) {
top: 12px;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(1) {
translate: 0 6px;
rotate: 45deg;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(2) {
opacity: 0;
}
.chm-16 .navbar-toggler[aria-expanded="true"] .chm-16__bars i:nth-child(3) {
translate: 0 -6px;
rotate: -45deg;
}
.chm-16 .navbar-collapse {
flex-basis: 100%;
overflow: hidden;
}
.chm-16 .navbar-collapse.collapse:not(.show) {
display: none;
}
.chm-16 .navbar-collapse.collapsing {
height: 0;
transition: height .3s ease;
}
.chm-16 .navbar-nav {
list-style: none;
display: flex;
flex-direction: column;
padding: 6px 4px 10px;
}
.chm-16 .nav-link {
display: block;
padding: 10px 12px;
border-radius: 8px;
font-size: 14.5px;
font-weight: 500;
color: var(--mut);
text-decoration: none;
transition: color .15s,background .15s;
}
.chm-16 .nav-link:hover,
.chm-16 .nav-link:focus-visible {
color: var(--ink);
background: oklch(0.94 0.006 260);
}
.chm-16 .nav-link:focus-visible {
outline: none;
box-shadow: 0 0 0 3px color-mix(in oklch,var(--primary) 35%,transparent);
}
.chm-16 .nav-link.active {
color: var(--ink);
font-weight: 600;
}
.chm-16 .nav-link.disabled {
color: oklch(0.72 0.008 262);
pointer-events: none;
}
@container (min-width: 768px) {
.chm-16 .navbar-toggler {
display: none;
}
.chm-16 .navbar-collapse {
flex-basis: auto;
}
.chm-16 .navbar-collapse.collapse:not(.show) {
display: flex !important;
}
.chm-16 .navbar-nav {
flex-direction: row;
gap: 2px;
padding: 0;
margin-left: auto;
}
}
.chm-16__page {
padding: 16px;
display: grid;
gap: 11px;
}
.chm-16__page span {
display: block;
height: 10px;
width: var(--w);
border-radius: 99px;
background: oklch(0.94 0.004 260);
}
.chm-16__note {
font-size: 12px;
line-height: 1.6;
color: var(--mut);
text-wrap: pretty;
}
.chm-16__note code {
font: 11px ui-monospace,Menlo,Consolas,monospace;
background: oklch(0.93 0.005 260);
border-radius: 5px;
padding: 1.5px 6px;
}
@media (prefers-reduced-motion: reduce) {
.chm-16 * {
transition-duration: .01s !important;
}
}
```
## JavaScript
```js
(function () {
/* Stand-in for bootstrap.bundle.min.js collapse — same class dance, same timing. */
document.querySelectorAll('.chm-16__frame').forEach(function (frame) {
if (frame.dataset.bound) return; frame.dataset.bound = '1';
var btn = frame.querySelector('[data-bs-toggle="collapse"]'),
panel = frame.querySelector(btn.getAttribute('data-bs-target'));
btn.addEventListener('click', function () {
var opening = !panel.classList.contains('show');
btn.setAttribute('aria-expanded', String(opening));
panel.classList.remove('collapse');
panel.classList.add('collapsing');
panel.style.height = opening ? '0' : panel.scrollHeight + 'px';
requestAnimationFrame(function () {
panel.style.height = opening ? panel.scrollHeight + 'px' : '0';
});
setTimeout(function () {
panel.classList.remove('collapsing');
panel.classList.add('collapse');
panel.classList.toggle('show', opening);
panel.style.height = '';
}, 300);
});
});
})();
```How this works
Bootstrap's navbar is a contract between five classes and two data attributes:
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">Brand</a>
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse" data-bs-target="#mainNav"
aria-controls="mainNav" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav ms-auto"> …nav-item / nav-link… </ul>
</div>
</div>
</nav>navbar-expand-lg = burger below 992px, row above (swap lg for md/sm/xl to move it). data-bs-target must be a SELECTOR (#mainNav, hash included) while aria-controls is an ID (no hash) — mixing those up is fix #1. Fix #2: the collapse div needs BOTH collapse and navbar-collapse — the first is the plugin hook, the second the responsive styling. Fix #3: the toggler does nothing without bootstrap.bundle.min.js — the bundle build, which includes Popper; the plain build silently ships no collapse plugin. Bootstrap's JS updates aria-expanded for you — it's one of the few frameworks that gets that right by default.
The default toggler is a static SVG background-image. The docs' replacement (used live in this card) swaps it for three animated bars in ~10 lines, keyed off Bootstrap's own aria-expanded state — no plugin changes.
This card renders through a scoped shim replicating the relevant 5.3 styles plus a 15-line stand-in for the collapse plugin, so the demo behaves exactly as Bootstrap's would; in your project, load real Bootstrap and delete both.
Make it yours
- Breakpoint:
navbar-expand-{sm|md|lg|xl|xxl}— one class, no CSS. No expand class at all = always-burger (fullscreen-nav sites). - Alignment:
ms-autoon navbar-nav pushes links right;mx-autocenters them; these are just margin utilities, nothing navbar-specific. - Smooth height animation comes free from the collapse plugin (it measures + transitions height); set
.navbar-collapse{ transition-duration:.25s }to retune it. - Dark navbar in 5.3:
data-bs-theme="dark"on the nav — the old navbar-dark class is deprecated; the attribute also recolors the default toggler icon.
Gotchas — read before shipping
- 'Menu opens then instantly closes' = the button is inside a form or has no
type="button", so it submits/reloads. Always declare the type. - Two navbars on one page must target different ids — data-bs-target="#mainNav" on both means one button drives both panels (collapse is selector-based, not proximity-based).
- Overriding navbar styles requires loading YOUR css after bootstrap.min.css and matching specificity — .navbar-nav .nav-link, not .nav-link. Half of 'my styles don't apply' is load order.
- The collapse animation needs the panel to start closed in markup (class="collapse", not "collapse show") — starting open then removing show on load causes the first-click double-fire confusion.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), color-mix(), @container, text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
Bootstrap 5.3 itself supports Chrome/Edge/Firefox/Safari of the last ~3 years (no IE). The shim here mirrors it with container queries + oklch for in-card rendering only.