- Tabs switch to accordions on small screens
- Remembers last open tab/accordion on page refresh (useful when you have a form inside a tab)
- Local storage is used for the persistence
- Plain JavaScript with debounced function
- Optional scroll to top of open accordion item on tap, with adjustable offset
- Optional one accordion item always open
- Optional Plus/Minus icons instead of chevrons (.accordion-button.plus classes)
Demo
Resize viewport < 768px or in Firefox, press CTRL + SHIFT + M to enter responsive design mode.
Content for Section 1
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
Content for Section 2
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
Content for Section 3
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
Markup
<div class="container my-3">
<ul id="myTab" class="nav nav-tabs justify-content-center d-none d-md-flex border p-3 pb-0 rounded-top border-bottom-0 bg-body-tertiary" role="tablist">
<li class="responsive nav-item px-1" role="presentation"><button id="tab-1" class="responsive nav-link text-reset p-3 py-2 fs-4 fw-semibold active" role="tab" type="button" data-bs-toggle="tab" data-bs-target="#pane-1">Section 1</button></li>
<li class="responsive nav-item px-1" role="presentation"><button id="tab-2" class="responsive nav-link text-reset p-3 py-2 fs-4 fw-semibold" role="tab" type="button" data-bs-toggle="tab" data-bs-target="#pane-2">Section 2</button></li>
<li class="responsive nav-item px-1" role="presentation"><button id="tab-3" class="responsive nav-link text-reset p-3 py-2 fs-4 fw-semibold" role="tab" type="button" data-bs-toggle="tab" data-bs-target="#pane-3">Section 3</button></li>
</ul>
<div class="responsive tab-content accordion" id="mainTabContent">
<div class="responsive accordion-item">
<div class="responsive accordion-header d-md-none"><button class="responsive accordion-button plus p-2 fs-3 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#pane-1"> Section 1 </button></div>
<div id="pane-1" class="responsive tab-pane show active accordion-collapse collapse" data-bs-parent="#mainTabContent">
<div class="responsive accordion-body p-3">
<h3 class="mt-0">Content for Section 1</h3>
<p class="m-0">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
</div>
</div>
</div>
<div class="responsive accordion-item">
<div class="responsive accordion-header d-md-none"><button class="responsive accordion-button plus p-2 fs-3 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#pane-2"> Section 2 </button></div>
<div id="pane-2" class="responsive tab-pane accordion-collapse collapse" data-bs-parent="#mainTabContent">
<div class="responsive accordion-body p-3">
<h3 class="mt-0">Content for Section 2</h3>
<p class="m-0">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.</p>
</div>
</div>
</div>
<div class="responsive accordion-item">
<div class="responsive accordion-header d-md-none"><button class="responsive accordion-button plus p-2 fs-3 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#pane-3"> Section 3 </button></div>
<div id="pane-3" class="responsive tab-pane accordion-collapse collapse" data-bs-parent="#mainTabContent">
<div class="responsive accordion-body p-3">
<h3 class="mt-0">Content for Section 3</h3>
<p class="m-0">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</p>
</div>
</div>
</div>
</div>
</div>CSS
@media (min-width: 768px) {
.responsive.accordion-item {
border: none !important;
border-bottom-left-radius: var(--bs-border-radius) !important;
border-bottom-right-radius: var(--bs-border-radius) !important;
}
.responsive.accordion-body {
border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;
border-bottom-left-radius: var(--bs-border-radius) !important;
border-bottom-right-radius: var(--bs-border-radius) !important;
}
}
.responsive.accordion-button:not(.collapsed),
.responsive.accordion-body .accordion-button:not(.collapsed) {
color: var(--bs-accordion-active-color) !important;
background-color: var(--bs-tertiary-bg) !important;
border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;
}
.responsive.accordion-button {
box-shadow: none !important;
transition: none !important;
}
[data-bs-theme="dark"] .accordion-button::after {
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ffffff'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ffffff'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e");
}
.accordion-button.plus::after {
--bs-accordion-btn-icon-width: 2rem;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23000' class='bi bi-plus' viewBox='0 0 16 16'%3E%3Cpath d='M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z'/%3E%3C/svg%3E") !important;
transition: all 0.5s;
}
.accordion-button.plus:not(.collapsed)::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23000' class='bi bi-dash' viewBox='0 0 16 16'%3E%3Cpath d='M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z'/%3E%3C/svg%3E") !important;
}
[data-bs-theme="dark"] .accordion-button.plus::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23fff' class='bi bi-plus' viewBox='0 0 16 16'%3E%3Cpath d='M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z'/%3E%3C/svg%3E") !important;
transition: all 0.5s;
}
[data-bs-theme="dark"] .accordion-button.plus:not(.collapsed)::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23fff' class='bi bi-dash' viewBox='0 0 16 16'%3E%3Cpath d='M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z'/%3E%3C/svg%3E") !important;
}JavaScript
function debounce(fn, wait) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), wait);
};
}
document.addEventListener("DOMContentLoaded", function() {
const storageKey = "activeTabSetting";
const tabPanes = document.querySelectorAll('.responsive.tab-pane');
const tabButtons = document.querySelectorAll('.responsive[data-bs-toggle="tab"]');
const accordionButtons = document.querySelectorAll('.responsive.accordion-button');
const scrollToTopAcc = false; // Scroll selected accordion item into view
const oneAccAlwaysOpen = false; // One accordion item always open
// Load saved state
function loadSavedState() {
const savedTarget = localStorage.getItem(storageKey);
if (savedTarget && document.querySelectorAll(savedTarget).length) {
const targetEl = document.querySelector(savedTarget);
if (targetEl) {
// Reset actives
document.querySelectorAll('.responsive.active, .responsive.show').forEach(el => el.classList.remove('active', 'show'));
// Activate Pane
targetEl.classList.add('active', 'show');
// Activate Tab Button
const tabBtn = document.querySelector(`[data-bs-target="${savedTarget}"][data-bs-toggle="tab"]`);
if (tabBtn) tabBtn.classList.add('active');
// Activate Accordion Button
const accBtn = document.querySelector(`[data-bs-target="${savedTarget}"].responsive.accordion-button`);
const accordionButtons = document.querySelectorAll('.responsive.accordion-button');
accordionButtons.forEach(btn => { btn.classList.add('collapsed'); });
if (accBtn) accBtn.classList.remove('collapsed');
}
}
}
loadSavedState();
window.addEventListener('resize', debounce(loadSavedState, 100));
// Update localStorage on change
function updateState(targetId) {
localStorage.setItem(storageKey, targetId);
// Ensure that if we click an accordion, the tab also looks active (and vice versa)
tabButtons.forEach(btn => {
btn.classList.toggle('active', btn.getAttribute('data-bs-target') === targetId);
});
}
// Listen for tab clicks
tabButtons.forEach(btn => {
btn.addEventListener('shown.bs.tab', e => updateState(e.target.getAttribute('data-bs-target')));
});
// Listen for accordion clicks
accordionButtons.forEach(btn => {
btn.addEventListener('click', function() {
const targetId = this.getAttribute('data-bs-target');
updateState(targetId);
// Scroll to top of accordion
if (scrollToTopAcc === true) {
setTimeout(() => {
const element = document.querySelector(targetId);
const offset = 60; // Padding from top
const bodyRect = document.body.getBoundingClientRect().top;
const elementRect = element.getBoundingClientRect().top;
const elementPosition = elementRect - bodyRect;
const offsetPosition = elementPosition - offset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
}, 350);
}
});
});
// Ensure at least one accordion is always open (optional)
if (oneAccAlwaysOpen === true) {
const accordions = document.querySelectorAll(".accordion-collapse");
let opening = false;
accordions.forEach(function (el) {
el.addEventListener("hide.bs.collapse", (event) => {
if (!opening) {
event.preventDefault();
event.stopPropagation();
} else {
opening = false;
}
});
el.addEventListener("show.bs.collapse", (event) => {
opening = true;
});
});
}
}); 