Bootstrap accordion uses data-bs-toggle="collapse" to toggle panels without custom JS.
Plus/minus icon is pure CSS: ::before pseudo-element with content swapping on .accordion-button:not(.collapsed).
data-bs-parent enforces mutual exclusivity — miss it on one panel and multiple panels open simultaneously.
ARIA attributes (aria-expanded, aria-controls, aria-labelledby) are required for screen readers and must match initial visual state.
Programmatic control via bootstrap.Collapse API requires { toggle: false } to avoid double-toggles on construction.
✦ Definition~90s read
What is Bootstrap Accordion?
The Bootstrap Accordion is a UI component that vertically stacks collapsible panels, allowing users to toggle visibility of content sections while optionally enforcing that only one panel is open at a time. It solves the problem of organizing dense information into digestible, space-efficient chunks without requiring page navigation or modal overlays.
★
Picture a stack of filing cabinet drawers.
Built on Bootstrap's Collapse plugin, the accordion uses a combination of data attributes (data-bs-toggle, data-bs-target, data-bs-parent) and CSS transitions to manage show/hide states. The 'plus/minus' icon pattern—where a + becomes a − when a panel opens—is not built into Bootstrap; you must implement it yourself via CSS pseudo-elements or JavaScript class toggling.
The 'frozen icon bug' occurs when the icon fails to update after a panel collapses, typically because the collapse event handlers (shown.bs.collapse, hidden.bs.collapse) aren't properly wired, or because the icon state is tied to a CSS class that doesn't survive Bootstrap's internal state management. This bug is especially common in dynamic accordions where panels are added or removed at runtime, as the event listeners or ARIA attributes (aria-expanded, aria-controls) may not be correctly re-initialized.
Understanding the underlying Collapse engine—which uses CSS transitions, max-height animations, and a state machine with collapsing, show, and hide classes—is critical for debugging icon flickering or stuck states. For production use, you should prefer the data-bs-parent attribute for single-open behavior over manual JavaScript, but when you need fine-grained control (e.g., preventing close on certain conditions, or syncing external UI), you'll drop down to the Collapse constructor or jQuery methods.
The ARIA contract is non-negotiable: each panel header must have aria-expanded toggled between true/false, and the content panel must have aria-labelledby pointing to the header's id. Skipping these breaks screen readers and can cause the icon state to desync from the actual visibility.
For dynamic panels, use new bootstrap.Collapse(element, {toggle: false}) to initialize without auto-toggling, then manually call .show() or .hide() after appending the DOM nodes.
Plain-English First
Picture a stack of filing cabinet drawers. Each drawer has a label on the front. You pull one open to see its contents, and the others stay shut — the cabinet doesn't explode every drawer at once just because you touched one. A Bootstrap accordion is exactly that for a webpage: a stack of labelled panels where clicking one opens it and the rest stay collapsed. The plus/minus icon is just the handle on the drawer — it tells you at a glance whether a drawer is open or shut.
⚙ Browser compatibility
Latest versions — ✓ supported
Chrome
Firefox
Safari
Edge
✓
✓
✓
✓
The most common Bootstrap accordion bug I've debugged across five different client projects wasn't a JavaScript error — it was a plus sign that never became a minus sign, because someone copy-pasted the HTML from the docs and didn't read the part about data attributes. The accordion opened and closed just fine, but the icon stayed frozen as a plus forever. Users assumed it was broken and called support. That was a $40/hour support ticket for six lines of CSS that should have been there from day one.
HTML pages with dense content — FAQs, product details, settings panels — have a real problem: they dump everything on screen at once and users scroll endlessly looking for the one thing they need. Before accordions existed as a proper pattern, developers were writing raw jQuery show/hide spaghetti, duplicating event listeners, and forgetting to handle keyboard navigation entirely. Accessibility auditors had a field day. The Bootstrap accordion component solves this with a consistent, accessible, keyboard-navigable collapsible panel system — and it does it without a single line of custom JavaScript if you wire it up correctly.
By the end of this article you'll be able to build a fully working Bootstrap 5 accordion with a real plus/minus icon that flips state on open and close, understand exactly which HTML attributes control the collapse behaviour, and know the three mistakes that make accordions silently break in production — before you ship them.
How Bootstrap Accordion Actually Works
A Bootstrap accordion is a vertically stacked list of collapsible panels that toggles visibility of content sections via the collapse plugin. The core mechanic: clicking a header triggers a show/hide transition on the associated body panel, while optionally collapsing any other open panel in the group. This is achieved through data attributes (data-bs-toggle="collapse", data-bs-target="#panelId") or JavaScript API calls. The collapse plugin uses CSS transitions on max-height and opacity, with a transition duration of 350ms by default. The parent accordion element must have an id and the .accordion class; each item uses .accordion-item, .accordion-header, and .accordion-collapse. The show class is toggled on the collapse element, and the collapsed class is toggled on the button to indicate state. The icon swap (plus/minus) is a common customization: you override the default chevron with two icons and toggle their visibility based on the collapsed class. The frozen icon bug occurs when the collapsed class is not updated synchronously with the collapse state, often due to race conditions with custom JavaScript or improper initialization.
⚠ Collapsed class timing
The collapsed class on the toggle button is not updated until after the transition completes — do not rely on it synchronously.
📊 Production Insight
Teams using custom JavaScript to toggle icons alongside Bootstrap's collapse events often see the icon stuck in 'plus' state after rapid clicks.
The symptom: the collapse panel opens but the icon remains a plus sign because the 'collapsed' class was toggled before the transition finished, causing a state mismatch.
Rule of thumb: always use Bootstrap's 'shown.bs.collapse' and 'hidden.bs.collapse' events to update icons, never 'click' or 'show.bs.collapse'.
🎯 Key Takeaway
The collapsed class is the single source of truth for icon state — never derive icon visibility from panel visibility.
Bootstrap's collapse events fire asynchronously; use 'shown.bs.collapse' and 'hidden.bs.collapse' for state-dependent UI updates.
Always initialize the accordion with JavaScript if you customize the collapse behavior — data attributes alone can miss edge cases.
thecodeforge.io
Bootstrap Accordion
How Bootstrap's Collapse Engine Actually Works Under the Hood
Before you touch a single accordion, you need to understand what's actually driving it — because the moment you don't, you'll spend an afternoon wondering why your panel won't open and the console is completely silent.
Bootstrap's accordion is built on top of its Collapse plugin. That plugin watches for click events on any element that has a data-bs-toggle="collapse" attribute. When clicked, it finds the target element — identified by data-bs-target or href — and toggles the CSS classes show and collapsed on the relevant elements. That's it. There's no magic. It's a class toggler with some CSS transitions baked in.
The accordion layer adds one rule on top: data-bs-parent. Set this attribute to the ID of the accordion's wrapper element, and Bootstrap will automatically close any currently open panel when you open a new one. Without data-bs-parent, every panel opens independently — which is sometimes what you want, but not what most people picture when they say 'accordion'.
Why does this matter practically? Because if you forget data-bs-parent on even one panel, that panel will open alongside others instead of replacing them. Users see two panels open at once, assume the component is broken, and your UX scores take a hit. I've seen this exact bug ship to production because a developer added a fourth panel to an existing three-panel accordion and didn't copy the attribute.
AccordionStructure.htmlHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<!-- io.thecodeforge — JavaScript tutorial -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BootstrapAccordion — TheCodeForge</title>
<!-- Bootstrap5CSS from CDN — no npm required forthis demo -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
/>
</head>
<body class="p-4">
<!--
WRAPPER: id="faqAccordion" is the anchor point for data-bs-parent.
Every panel inside MUST reference thisID or the
'only one open at a time' behaviour will silently break.
-->
<div class="accordion" id="faqAccordion">
<!-- ── PANEL1 ─────────────────────────────────────── -->
<div class="accordion-item">
<!--
accordion-header wraps the button.
The id here ("headingShipping") is referenced by
aria-labelledby on the body panel below — required for screen readers.
-->
<h2 class="accordion-header" id="headingShipping">
<button
class="accordion-button"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseShipping"
aria-expanded="true"
aria-controls="collapseShipping"
>
Howlong does shipping take?
</button>
</h2>
<!--
accordion-collapse is the panel body wrapper.
'show'class = visible on page load (matches aria-expanded="true" above).
data-bs-parent="#faqAccordion" is what enforces mutual exclusivity.
NEVER omit this — see callout below.
-->
<div
id="collapseShipping"class="accordion-collapse collapse show"
aria-labelledby="headingShipping"
data-bs-parent="#faqAccordion"
>
<div class="accordion-body">
Standard shipping takes 3–5 business days.
Express options are available at checkout.
</div>
</div>
</div>
<!-- ── ENDPANEL1 ──────────────────────────────────── -->
<!-- ── PANEL2 ─────────────────────────────────────── -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingReturns">
<!--
No'show'class on this button's panel = starts collapsed.
aria-expanded="false" must match — Bootstrap won't auto-correct this.
-->
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseReturns"
aria-expanded="false"
aria-controls="collapseReturns"
>
What is your return policy?
</button>
</h2>
<div
id="collapseReturns"class="accordion-collapse collapse"
aria-labelledby="headingReturns"
data-bs-parent="#faqAccordion"
>
<div class="accordion-body">
Returns are accepted within 30 days of delivery.
Items must be unused and in original packaging.
</div>
</div>
</div>
<!-- ── ENDPANEL2 ──────────────────────────────────── -->
</div>
<!-- ── ENDACCORDIONWRAPPER ───────────────────────────── -->
<!-- Bootstrap5JSbundle (includes Popper) — must be AFTER the HTML -->
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js">
</script>
</body>
</html>
Output
Page renders with two accordion panels.
Panel 1 ('How long does shipping take?') is open by default.
Panel 2 ('What is your return policy?') is collapsed.
Clicking Panel 2 opens it and simultaneously closes Panel 1.
Clicking Panel 1 again reopens it and closes Panel 2.
Keyboard navigation (Tab + Enter/Space) works without any custom JS.
⚠ Production Trap: Missing data-bs-parent Breaks Mutual Exclusivity Silently
If you omit data-bs-parent="#faqAccordion" from even one panel's accordion-collapse div, that panel will open alongside others instead of replacing them. The console shows zero errors. The only symptom is two panels open at once — which users report as 'the accordion is broken'. Fix: grep your accordion HTML for every accordion-collapse element and confirm every single one carries the matching data-bs-parent attribute.
📊 Production Insight
I once saw a four-panel accordion where only the first panel had data-bs-parent. The developer added three more panels thinking the attribute was inherited. It's not.
The result: panels 2, 3, and 4 opened independently while panel 1 closed as expected. Users saw a mix of open panels and called support.
Rule: data-bs-parent is per-element, not inherited. Every single panel must have it.
🎯 Key Takeaway
The data-bs-parent attribute must be present on every accordion-collapse div.
Without it, mutual exclusivity breaks silently.
Audit new panels with a search: grep 'accordion-collapse' and verify the attribute exists on each.
The Plus/Minus Toggle: Why Bootstrap Doesn't Give You This for Free
Here's something the Bootstrap docs bury in a footnote: the default accordion uses a CSS chevron (›) that rotates on open/close — it does NOT give you a plus/minus toggle out of the box. If your design calls for a + when collapsed and a − when expanded, you're doing that yourself. That's not a criticism of Bootstrap; it's just how it is. Know it going in.
The mechanism Bootstrap does give you for free is the collapsed class. When a panel is closed, the trigger button carries the class collapsed. When it's open, that class is absent. You can hook into this with CSS to swap any icon you want — no JavaScript required.
The cleanest production approach is to use CSS content on a pseudo-element tied to the button's state. You put your plus character as the default content, and you override it to a minus when the collapsed class is absent (meaning the panel is open). This is a single CSS rule pair. It costs you nothing at runtime, it works with Bootstrap's existing class-toggling behaviour, and it survives framework upgrades because it relies on the documented collapsed class — not internal Bootstrap implementation details that change between minor versions.
AccordionPlusMinus.htmlHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
<!-- io.thecodeforge — JavaScript tutorial -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FAQAccordion — Plus/MinusToggle</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
/>
<style>
/*
* Step1: KillBootstrap's default rotating chevron.
* Bootstrap injects it via .accordion-button::after using a background-image.
* Setting background-image to none removes it entirely.
* We also clear the transform so Bootstrap's rotate animation doesn't fire.
*/
.accordion-button::after {
background-image: none !important;
transform: none !important;
content: '';
}
/*
* Step2: Create our own icon element via ::before.
* We use ::before (not ::after) so the icon sits on the LEFT
* of the button text, matching the design spec forthis example.
* Swap to ::after and adjust margin if your design puts it on the right.
*/
.accordion-button::before {
/*
* Default state = button is COLLAPSED = panel is CLOSED.
* Show a plus sign.
*/
content: '+';
font-size: 1.25rem;
font-weight: 700;
line-height: 1;
margin-right: 0.75rem;
/*
* Prevent the icon from shifting layout when it changes character.
* A minus sign is narrower than a plus in most fonts — inline-block
* with a fixed width keeps the text from jumping left on open.
*/
display: inline-block;
width: 1rem;
text-align: center;
/*
* Smooth the character swap with opacity — purely cosmetic but
* removes the jarring instant change that looks like a bug.
*/
transition: opacity 0.15s ease-in-out;
}
/*
* Step3: When the button does NOT have the 'collapsed'class,
* the panel is OPEN. Swap the plus for a minus.
*
* Key insight: BootstrapADDS'collapsed' when closing and REMOVES it
* when opening. So'no collapsed class' = open state.
* We target the ABSENCE of the class, not its presence.
*/
.accordion-button:not(.collapsed)::before {
content: '\2212'; /* Unicode minus sign — visually wider than a hyphen */
}
/* Optional: remove Bootstrap's default blue focus outline colour change */
.accordion-button:not(.collapsed) {
color: inherit;
background-color: #f8f9fa;
box-shadow: none;
}
</style>
</head>
<body class="p-4">
<h1 class="mb-4">FrequentlyAskedQuestions</h1>
<div class="accordion" id="productFaqAccordion">
<!-- ── PANEL1: StartsOPEN ────────────────────────── -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingPayment">
<button
class="accordion-button"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapsePayment"
aria-expanded="true"
aria-controls="collapsePayment"
>
What payment methods do you accept?
</button>
</h2>
<div
id="collapsePayment"class="accordion-collapse collapse show"
aria-labelledby="headingPayment"
data-bs-parent="#productFaqAccordion"
>
<div class="accordion-body">
We accept Visa, Mastercard, PayPal, and bank transfer.
All transactions are encrypted with TLS1.3.
</div>
</div>
</div>
<!-- ── PANEL2: StartsCOLLAPSED ─────────────────────── -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingWarranty">
<!--
'collapsed'class is present here because this panel starts closed.
This means our CSSdefault (plus sign) shows immediately on load.
If you forget thisclass, the button shows a minus sign on load
even though the panel is closed — that's the classic mismatch bug.
-->
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseWarranty"
aria-expanded="false"
aria-controls="collapseWarranty"
>
Does my purchase include a warranty?
</button>
</h2>
<div
id="collapseWarranty"class="accordion-collapse collapse"
aria-labelledby="headingWarranty"
data-bs-parent="#productFaqAccordion"
>
<div class="accordion-body">
All hardware products carry a 24-month manufacturer warranty.
Software licences are covered under our 30-day satisfaction guarantee.
</div>
</div>
</div>
<!-- ── PANEL3: StartsCOLLAPSED ─────────────────────── -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingCancellation">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseCancellation"
aria-expanded="false"
aria-controls="collapseCancellation"
>
Can I cancel my subscription at any time?
</button>
</h2>
<div
id="collapseCancellation"class="accordion-collapse collapse"
aria-labelledby="headingCancellation"
data-bs-parent="#productFaqAccordion"
>
<div class="accordion-body">
Yes. Cancel from your account dashboard any time.
You keep access until the end of your billing period.
</div>
</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js">
</script>
</body>
</html>
Output
Page loads with Panel 1 open, showing a '−' icon on its button.
Panels 2 and 3 are collapsed, each showing a '+' icon.
Clicking Panel 2: Panel 1 closes (icon flips to '+'), Panel 2 opens (icon flips to '−').
💡Senior Shortcut: Use Unicode \2212 for Minus, Not a Hyphen
The hyphen character (-) is narrower than a plus sign (+) in almost every font. Using a hyphen as your 'minus' makes the button text visually jump left when the panel opens because the icon shrinks. Use the actual Unicode minus sign \2212 in your CSS content property — it's the same width as a plus and looks intentional rather than hacked together.
📊 Production Insight
In a production settings panel, the plus/minus swap looked fine in Chrome but broke in Firefox because the hyphen width varied. The text jittered every time a panel opened.
Switching to \2212 fixed it in all browsers.
Rule: when swapping icons with CSS content, use Unicode characters with consistent width, or set a fixed width on the pseudo-element.
🎯 Key Takeaway
The collapsed class is your hook — Bootstrap adds/removes it on every toggle.
Use .accordion-button:not(.collapsed)::before to swap icons with zero JS.
Always use Unicode minus \2212 for the open state to avoid layout shifts.
thecodeforge.io
Bootstrap Accordion
Accessibility and the ARIA Attributes You Can't Skip
I've watched a Bootstrap accordion sail through QA and get flagged on the first accessibility audit because the developer treated aria-expanded as decorative. It's not. Screen readers use aria-expanded to announce whether a panel is open or closed — without it, a visually impaired user hears a button with no context and has to guess what it controls.
The good news: Bootstrap's Collapse plugin automatically toggles aria-expanded for you between true and false as panels open and close. Your only job is to make sure the initial HTML state is honest. If a panel starts collapsed, aria-expanded must be false on load. If it starts open, it must be true. Bootstrap doesn't backfill this on page load — it only manages it after the first interaction.
The other attribute people skip is aria-controls. This tells assistive technology which element the button controls — it should match the id of the accordion-collapse div. And the aria-labelledby on the collapse div should point back to the accordion-header id. These two form a two-way relationship that lets screen readers navigate the accordion as a coherent structure rather than a pile of unrelated buttons and divs. Skip either one and you'll fail WCAG 2.1 Level AA — which matters if you're building anything for a government contract, healthcare platform, or any company that takes accessibility law seriously.
AccordionAccessibility.htmlHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<!-- io.thecodeforge — JavaScript tutorial -->
<!--
This shows the complete ARIA relationship map for one accordion panel.
Annotated specifically to show what each accessibility attribute does
and what breaks if you remove it.
-->
<div class="accordion" id="settingsAccordion">
<div class="accordion-item">
<!--
aria-labelledby on the collapse div below will point to THIS id.
The heading id must be unique per page — not just per accordion.
Using a descriptive id ('headingNotifications') beats 'heading1'
because it's readable in accessibility tree inspectors.
-->
<h2 class="accordion-header" id="headingNotifications">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseNotifications"
<!--
aria-expanded MUST match visual state on load.
'false' here = panel is visually collapsed = correct.
Bootstrap updates this attribute dynamically after first click.
If you ship aria-expanded="false" on an open panel,
screen readers announce it as closed — users can't trust the page.
-->
aria-expanded="false"
<!--
aria-controls = id of the panel this button controls.
Screen readers use this to let users jump directly to the
content without having to tab through every element.
-->
aria-controls="collapseNotifications"
>
NotificationPreferences
</button>
</h2>
<div
id="collapseNotifications"class="accordion-collapse collapse"
<!--
aria-labelledby creates the reverse link:
this panel announces it is labelled by the heading button above.
Must match the accordion-header's id exactly — case-sensitive.
-->
aria-labelledby="headingNotifications"
data-bs-parent="#settingsAccordion"
>
<div class="accordion-body">
<p>Choose how and when you receive alerts.</p>
<!-- Real settings form would live here -->
<div class="form-check">
<input class="form-check-input" type="checkbox" id="emailAlerts" />
<label class="form-check-label"for="emailAlerts">
Email alerts for order updates
</label>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="smsAlerts" />
<label class="form-check-label"for="smsAlerts">
SMS alerts for shipping changes
</label>
</div>
</div>
</div>
</div>
</div>
⚠ Never Do This: aria-expanded Mismatch on Page Load
If a panel starts visually open (has the show class) but its button has aria-expanded="false", screen readers announce the panel as closed. Users navigate away thinking there's no content. Bootstrap will NOT fix this mismatch on load — it only manages aria-expanded after the first user interaction. The symptom is invisible in a browser but will fail any WCAG audit instantly. Rule: show class on the panel = aria-expanded="true" on the button. No exceptions.
📊 Production Insight
During an accessibility audit for a health insurance portal, the QA tested with NVDA. The first panel was open on load but aria-expanded was false. The screen reader said 'Apply online, collapsed, button'. Users would never know the form was visible.
The fix was a one-character change from false to true in the HTML.
Rule: always double-check initial ARIA state matches the show class.
🎯 Key Takeaway
aria-expanded must match show class on page load — Bootstrap won't correct it.
aria-controls links button to panel content for screen readers.
aria-labelledby on the panel points back to the heading — complete the two-way link.
Controlling the Accordion with JavaScript When HTML Attributes Aren't Enough
Data attributes get you 90% of the way there. But there are real production scenarios where you need JavaScript control — opening a specific panel based on a URL hash, programmatically closing all panels when a user submits a form, or listening to open/close events to trigger analytics tracking.
Bootstrap exposes a JavaScript API for the Collapse component. You get a bootstrap.Collapse instance either by constructing one with new bootstrap.Collapse(element, options) or by grabbing one that already exists with bootstrap.Collapse.getInstance(element). The key methods are .show(), .hide(), and .toggle(). The key events are show.bs.collapse, shown.bs.collapse, hide.bs.collapse, and hidden.bs.collapse — fired on the collapse element itself, not on the button.
One thing that trips people up: if you construct a new bootstrap.Collapse(element) without passing { toggle: false }, Bootstrap immediately toggles the panel on construction. That means creating an instance to attach an event listener unintentionally opens the panel. Always pass { toggle: false } when you're constructing an instance purely to get programmatic control or listen to events.
AccordionJavaScriptControl.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// io.thecodeforge — JavaScript tutorial// ─── SCENARIO ───────────────────────────────────────────────────────────────// A product page accordion where:// 1. The correct panel auto-opens based on the URL hash (e.g. #collapseWarranty)// 2. All panels can be force-closed when the user hits 'Reset filters'// 3. A 'panel opened' event is sent to analytics// ─────────────────────────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', function () {
// ── 1. OPEN PANEL FROM URL HASH ──────────────────────────────────────────//// If the URL is /product#collapseWarranty, open that panel automatically.// Useful for deep-linking from emails or support articles.
const urlHash = window.location.hash; // e.g. "#collapseWarranty"if (urlHash) {
const targetPanel = document.querySelector(urlHash);
if (targetPanel && targetPanel.classList.contains('accordion-collapse')) {
/*
* { toggle: false } is critical here.
* Without it, constructing the instance triggers an immediate toggle —
* which would open ANDTHENCLOSE the panel before the user sees anything.
* With toggle: false, we construct the instance without touching panel state.
*/
const collapseInstance = new bootstrap.Collapse(targetPanel, {
toggle: false
});
// Now explicitly open it — clean, predictable, one-way
collapseInstance.show();
}
}
// ── 2. FORCE-CLOSE ALL PANELS ON 'RESET' ────────────────────────────────//// A 'Reset filters' button that also collapses all open accordion panels// to return the UI to its default state.const resetButton = document.getElementById('resetFiltersButton');
if (resetButton) {
resetButton.addEventListener('click', function () {
// Grab every open panel (has the 'show' class)const openPanels = document.querySelectorAll(
'#productFaqAccordion .accordion-collapse.show'
);
openPanels.forEach(function (panel) {
/*
* getInstance returns the existing Collapse instance ifBootstrap
* already manages this element. Returnsnullif no instance exists yet.
* Using getInstance avoids creating duplicate instances,
* which can cause double-fire events inBootstrap5.
*/
const existingInstance = bootstrap.Collapse.getInstance(panel);
if (existingInstance) {
existingInstance.hide();
} else {
// No instance yet — create one just to hide, without toggling on constructnew bootstrap.Collapse(panel, { toggle: false }).hide();
}
});
});
}
// ── 3. ANALYTICS EVENT ON PANEL OPEN ─────────────────────────────────────//// Fire a tracking event every time a panel finishes opening.// Using 'shown.bs.collapse' (past tense) rather than 'show.bs.collapse'// means the animation has completed — the panel is fully visible.// This avoids tracking panels the user flicked open and immediately closed// before the animation finished.const accordionWrapper = document.getElementById('productFaqAccordion');
if (accordionWrapper) {
accordionWrapper.addEventListener('shown.bs.collapse', function (event) {
/*
* event.target is the collapse panel element that just opened.
* Its id is the panel identifier we use to label the analytics event.
*/
const openedPanelId = event.target.id;
// Replace with your actual analytics call (GA4, Segment, Mixpanel, etc.)
console.log('[Analytics] Accordion panel opened:', openedPanelId);
/*
* Real-world example:
* window.dataLayer.push({
* event: 'accordion_panel_opened',
* panel_id: openedPanelId,
* page_path: window.location.pathname
* });
*/
});
}
});
/*
* BOOTSTRAPCOLLAPSEEVENTS — QUICKREFERENCE
* ─────────────────────────────────────────────────────────────
* show.bs.collapse — fires when show() is called (animation starts)
* shown.bs.collapse — fires when panel is fully open (animation ends)
* hide.bs.collapse — fires when hide() is called (animation starts)
* hidden.bs.collapse — fires when panel is fully closed (animation ends)
*
* All four fire on the PANELELEMENT, not on the trigger button.
* Delegatefrom the accordion wrapper to catch all panels in one listener.
*/
Output
URL /product#collapseWarranty → Warranty panel opens automatically on load.
'Reset filters' button click → all open panels collapse cleanly.
Each time a panel finishes opening → console logs:
⚠ The Classic Bug: new bootstrap.Collapse() Without { toggle: false }
Every time I've seen a Bootstrap panel mysteriously open and immediately close on its own, the culprit is new bootstrap.Collapse(element) without { toggle: false }. Bootstrap treats construction as implicit activation — so it toggles the panel the moment you create the instance. Then your .show() call toggles it again, closing it. Always pass { toggle: false } when constructing an instance for programmatic control. Always use bootstrap.Collapse.getInstance() first to avoid creating a second instance on an element Bootstrap already manages.
📊 Production Insight
I once integrated an accordion with a URL hash feature. The code looked clean: construct with new bootstrap.Collapse(targetPanel).show(). But on page load, the panel opened and immediately closed — visible flicker. The constructor fired one toggle, then .show() fired another.
The fix: add { toggle: false } to the constructor.
Rule: when constructing a Collapse instance, always pass options with { toggle: false } unless you explicitly want an immediate toggle.
🎯 Key Takeaway
Construct bootstrap.Collapse with { toggle: false } to avoid double-toggles.
Use getInstance to reuse existing instances — never create two on the same element.
Listen on the wrapper for shown.bs.collapse to track analytics after animation completes.
Dynamic Accordion: Adding and Removing Panels at Runtime with JavaScript
Static accordions are fine for fixed content like FAQs. But in production, you'll often need to add panels dynamically — think product filters that load options from an API, or a settings page where user roles determine which panels appear. Doing this with Bootstrap requires careful handling because the Collapse plugin doesn't automatically listen for new DOM nodes.
When you add a new accordion-item to an existing accordion, Bootstrap won't know about it until the user clicks the button. That's fine for data-attribute-driven toggling — Bootstrap listens globally on the document for clicks on [data-bs-toggle="collapse"]. So new panels will work for toggling as soon as they're inserted. But there's a catch: the data-bs-parent attribute needs to point to the correct wrapper ID, and if you're adding multiple panels, you must ensure each has a unique ID for the collapse targets and heading IDs.
For programmatic control over dynamically added panels, you need to create Collapse instances after insertion. There's a gotcha: if you call new bootstrap.Collapse() on a panel that's still being animated (e.g., you add it while another panel is opening), the methods may queue unexpectedly. The safe pattern is to insert the panel, wait a frame via requestAnimationFrame or setTimeout(fn, 0), then initialise the Collapse instance.
Also, when removing panels, you must destroy the Collapse instance with .dispose() to clean up event listeners. Failure to do so causes memory leaks and can break other panels if event handlers reference removed elements.
AccordionDynamicPanels.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// io.thecodeforge — JavaScript tutorial// ─── SCENARIO: Load product categories from API and add as accordion panels ──// Each category has a name and description. We add them dynamically.const accordionWrapper = document.getElementById('categoryAccordion');
let panelCounter = 0; // For unique IDs
/**
* Add a single accordion panel from data.
* @param {string} headingText - The button label
* @param {string} bodyContent - HTML content for the body
* @param {boolean} openInitially - Whetherthis panel starts open
*/
functionaddAccordionPanel(headingText, bodyContent, openInitially = false) {
panelCounter++;
const headingId = `headingDynamic${panelCounter}`;
const collapseId = `collapseDynamic${panelCounter}`;
// Create the accordion-item elementconst item = document.createElement('div');
item.className = 'accordion-item';
// Headerconst header = document.createElement('h2');
header.className = 'accordion-header';
header.id = headingId;
const button = document.createElement('button');
button.className = `accordion-button${openInitially ? '' : ' collapsed'}`;
button.type = 'button';
button.dataset.bsToggle = 'collapse';
button.dataset.bsTarget = `#${collapseId}`;
button.setAttribute('aria-expanded', openInitially ? 'true' : 'false');
button.setAttribute('aria-controls', collapseId);
button.textContent = headingText;
header.appendChild(button);
item.appendChild(header);
// Collapse bodyconst collapse = document.createElement('div');
collapse.id = collapseId;
collapse.className = `accordion-collapse collapse${openInitially ? ' show' : ''}`;
collapse.setAttribute('aria-labelledby', headingId);
collapse.dataset.bsParent = `#${accordionWrapper.id}`;
const body = document.createElement('div');
body.className = 'accordion-body';
body.innerHTML = bodyContent;
collapse.appendChild(body);
item.appendChild(collapse);
// Insert into DOM
accordionWrapper.appendChild(item);
// If we need programmatic control later, create Collapse instance// Wait a frame to ensure DOM is readyrequestAnimationFrame(() => {
if (openInitially) {
// Panel is already shown via 'show' class, but we may want to manage it// This instance will be used for future hide/show callsnew bootstrap.Collapse(collapse, { toggle: false });
} else {
// Bootstrap will create an instance lazily on first click — that's fine
}
});
return item; // Return reference for future removal
}
/**
* Remove an accordion panel and clean up its Collapse instance.
* @param {HTMLElement} panelItem - The .accordion-item element to remove
*/
functionremoveAccordionPanel(panelItem) {
const collapseEl = panelItem.querySelector('.accordion-collapse');
if (collapseEl) {
const instance = bootstrap.Collapse.getInstance(collapseEl);
if (instance) {
instance.dispose(); // Clean up event listeners
}
}
panelItem.remove();
}
// ─── EXAMPLE USAGE ────────────────────────────────────────────────────────// fetch categories from APIsetTimeout(() => {
addAccordionPanel('Electronics', 'Phones, Laptops, Tablets', true);
addAccordionPanel('Clothing', 'Shirts, Pants, Accessories', false);
addAccordionPanel('Books', 'Fiction, Non-fiction, Children\'s', false);
}, 500);
// Later, remove first panel// const item = document.querySelector('#categoryAccordion .accordion-item');// removeAccordionPanel(item);
Output
After 500ms, three new panels appear in the accordion.
Panel 'Electronics' is open by default, showing a minus icon.
The other two are closed with plus icons.
Clicking any panel toggles it and closes others (mutual exclusivity works).
Removing a panel with `removeAccordionPanel` cleans up its Collapse instance.
Think of Bootstrap's Collapse as a global click listener on [data-bs-toggle="collapse"] — not a watcher on individual elements.
When you add a new panel with the correct data attributes, it works immediately without JS re-initialisation — because the global listener catches the click.
However, if you need programmatic control (.show(), .hide()) on a dynamically added panel, you must create a Collapse instance after insertion.
Always delay instance creation by one frame (requestAnimationFrame) to avoid race conditions with DOM insertion.
When removing a panel, call .dispose() on the Collapse instance to prevent memory leaks — event listeners can outlive the DOM element.
📊 Production Insight
In a dashboard with user-configurable widgets, adding a new accordion panel worked for clicks but broke programmatic close-all on logout. Because no Collapse instance existed for the dynamically added panel, getInstance returned null and .hide() failed silently.
The fix: create instances for all dynamic panels after insertion.
Rule: if you need programmatic control over dynamic panels, initialise them explicitly with { toggle: false }.
🎯 Key Takeaway
Data-attribute accordions work on new panels automatically.
For programmatic control, create a Collapse instance after insertion.
Always .dispose() when removing panels to prevent memory leaks.
The URL Hash Hack: Opening Accordions From External Links
Your docs link to #faq-3 but nothing happens. The page scrolls, sure, but the accordion stays shut. That's not a feature, it's a UX trap.
Bootstrap's collapse engine listens for data attributes and click events, not hashchange. When a user arrives with #panel-3 in the URL, the browser fires hashchange before your accordion JavaScript initializes. The DOM is ready, but Bootstrap hasn't attached its event listeners yet.
The fix is dead simple: intercept the hash on load and manually trigger the collapse. Don't wait for DOMContentLoaded — wait for Bootstrap's collapse plugin to finish initializing. That means either deferring to window.onload or using a MutationObserver on the accordion parent.
Here's the pattern I've used on production sites handling customer support pages with 50+ FAQ entries. It works with both Bootstrap 4 and 5, and it doesn't fight the framework.
Never run this inside DOMContentLoaded. Bootstrap's Collapse constructor throws if the plugin hasn't initialized. Use window.load or a 50ms setTimeout as a last resort.
🎯 Key Takeaway
If you need accordion panels to open from URLs, intercept the hash on window.load, not DOMContentLoaded.
thecodeforge.io
Bootstrap Accordion
Nesting Accordions Without Breaking the Universe
Client wants a master-detail layout inside an accordion. Each parent panel opens to reveal three child accordions. Your first instinct? Wrap a second .accordion inside the .accordion-collapse div. That works until someone opens a child panel and the parent collapses on them.
Here's the problem: Bootstrap's accordion uses a click event on the parent container (the element with id matching the data-bs-parent). When you click a child accordion's header, the event bubbles up, and Bootstrap's collapse logic thinks you're trying to close the parent. Result: the parent snaps shut.
The fix is to stop propagation on the child accordion's click events. But don't just slap event.stopPropagation() on every header — that breaks keyboard navigation and ARIA. Instead, use e.stopPropagation() only on the child accordion's toggle buttons, and ensure the child accordion does NOT have a data-bs-parent attribute (so it behaves as independent collapses).
This is not a hack. It's how Bootstrap's event delegation model was designed to be overridden.
NestedAccordionFix.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — javascript tutorial// Prevents nested accordion clicks from collapsing parent panelsconst nestedAccordions = document.querySelectorAll('.nested-accordion');
nestedAccordions.forEach((accordion) => {
// Remove data-bs-parent to make child panels independent
accordion.removeAttribute('data-bs-parent');
// Stop event propagation on toggle buttons only
accordion.querySelectorAll('.accordion-button').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
});
});
});
Output
Nested accordion panels open and close independently. Parent accordion stays open when interacting with child panels.
Instead of removing data-bs-parent via JS, omit it entirely in your HTML for nested accordions. Set it to null and let Bootstrap treat each panel as a standalone collapse.
🎯 Key Takeaway
Nest accordions by removing data-bs-parent from children and stopping event propagation on child toggle buttons only.
● Production incidentPOST-MORTEMseverity: high
The Frozen Plus Sign — A Production Support Nightmare
Symptom
All accordion buttons displayed a plus icon regardless of panel state. Clicking a panel opened it, but the icon never changed to a minus. The panel content worked fine.
Assumption
Developers assumed Bootstrap automatically managed the icon because the docs showed a rotating chevron. They didn't realise the plus/minus was custom CSS they had to write — and their CSS selectors were targeting the wrong class.
Root cause
The CSS used .accordion-button::after { content: '+'; } but never added the overriding rule for the expanded state. The button's collapsed class was being added/removed correctly, but the CSS didn't hook into it. Also, the background-image from Bootstrap was not cleared, so the default chevron overlay remained visible underneath the plus sign.
Fix
1. Remove Bootstrap's default chevron with background-image: none. 2. Use ::before (or ::after) for the custom icon. 3. Add .accordion-button:not(.collapsed)::before { content: '−'; } using Unicode minus \2212. 4. Test with both open and closed initial states.
Key lesson
Never assume Bootstrap handles icon state out of the box — it only toggles the collapsed class.
Always verify the CSS selector chain works for both collapsed (closed) and not(.collapsed) (open) states.
Use the browser's DevTools to inspect the button element and confirm which class is present at each state.
Production debug guideQuick guide for the most common Bootstrap accordion issues in production5 entries
Symptom · 01
Plus/minus icon never changes when panel opens/closes
→
Fix
Check CSS: ensure you have background-image: none !important on .accordion-button::after and your custom content rule for the expanded state uses .accordion-button:not(.collapsed)::before. Verify the collapsed class is applied on page load for closed panels.
Symptom · 02
Multiple panels open at the same time in an accordion
→
Fix
Inspect every accordion-collapse div. Missing data-bs-parent="#accordionId" on even one panel causes independent opening. Use DevTools element search for 'accordion-collapse' and verify each has the attribute.
Symptom · 03
Panel doesn't open at all on click, no console error
→
Fix
Check that data-bs-toggle="collapse" and data-bs-target="#panelId" are present on the button. Ensure the target ID matches an existing accordion-collapse with the same ID. Also ensure Bootstrap JS is loaded (not just CSS).
Symptom · 04
Panel opens and immediately closes on first click
→
Fix
Look for JavaScript code that calls new bootstrap.Collapse(element) without { toggle: false }. This creates an instant toggle. Switch to bootstrap.Collapse.getInstance(element) or add { toggle: false }.
Symptom · 05
Screen reader announces wrong panel state
→
Fix
Check aria-expanded on the button against the show class on the panel. If initial state is open, aria-expanded="true" and panel has show. Bootstrap only updates aria-expanded after first user interaction, not on load.
★ Accordion Quick-Fix Command ReferenceRun these commands in the browser console or terminal to diagnose common accordion issues without digging through docs.
Icon stuck as plus for all panels−
Immediate action
Open DevTools Elements panel, select an accordion button. Check if `collapsed` class is present when panel is closed and absent when open. If classes correct, CSS is the problem.
// If getInstance returns null, no instance yet. Check your JS for `new bootstrap.Collapse()`
Fix now
Replace new bootstrap.Collapse(panel) with new bootstrap.Collapse(panel, { toggle: false }) or use bootstrap.Collapse.getOrCreateInstance(panel, { toggle: false }).show().
Screen reader says 'closed' for an open panel on page load+
Immediate action
Inspect the button's `aria-expanded` attribute and the panel's `show` class.
The collapsed class on the button is your CSS hook
.accordion-button:not(.collapsed)::before is all you need to flip a plus to a minus with zero JavaScript.
2
data-bs-parent must be present on every single accordion-collapse div in your accordion
miss it on one panel and you get silent multi-open behaviour that only your users will notice.
3
Always pass `{ toggle
false } when constructing a bootstrap.Collapse instance in JavaScript — the constructor toggles the panel immediately by default, which will visually break your UI if you then call .show() or .hide()` right after.
4
Bootstrap does not fix aria-expanded mismatches on page load
only after the first interaction. If your panel starts open, aria-expanded must be true in the raw HTML or screen readers will announce the page incorrectly from the first second.
5
When adding panels dynamically, create Collapse instances after insertion with `{ toggle
false }` and dispose them on removal to prevent memory leaks.
Common mistakes to avoid
3 patterns
×
Forgetting the 'collapsed' class on the button for panels that start closed
Symptom
The button renders with a minus icon on page load even though the panel is shut, because the CSS targets .accordion-button:not(.collapsed) — the minus state. Users see all panels as open visually, even though content is hidden.
Fix
Every button whose panel starts without the show class must also have class="accordion-button collapsed". Check your HTML: if the panel does not have class="show", the button must have class="collapsed".
×
Omitting `data-bs-parent` on one or more panels
Symptom
Multiple panels open simultaneously, no console error. Users report 'accordion is broken' because clicking a new panel doesn't close the previous one.
Fix
Grep every accordion-collapse element in your HTML and confirm data-bs-parent points to the correct wrapper ID. It must be present on every panel, not just the first.
×
Calling `new bootstrap.Collapse(panel)` without `{ toggle: false }` inside a click handler
Symptom
Panel opens and immediately closes, appearing to ignore the click. The constructor triggers one toggle and your explicit .show() call triggers a second, cancelling each other.
Fix
Always construct with new bootstrap.Collapse(panel, { toggle: false }), or use bootstrap.Collapse.getInstance(panel) to retrieve the existing managed instance instead of creating a new one.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
Bootstrap's accordion relies on the 'collapsed' CSS class to manage icon...
Q02SENIOR
When would you choose to implement accordion open/close behaviour with v...
Q03SENIOR
If you fire `new bootstrap.Collapse(element).show()` on a panel that Boo...
Q01 of 03SENIOR
Bootstrap's accordion relies on the 'collapsed' CSS class to manage icon state. What happens if a developer initialises an accordion panel as open in the HTML but forgets to remove the 'collapsed' class from the trigger button — and how does this affect both the visual icon and screen reader behaviour?
ANSWER
The panel will appear open (has show class on the collapse div), but the button will have collapsed class. The CSS rule .accordion-button:not(.collapsed)::before will NOT apply, so the icon shows a plus instead of a minus — visually wrong. For screen readers, if aria-expanded="true" is set correctly, the state will be announced correctly, but the icon mismatch degrades the visual user experience. If aria-expanded is also set to false (common copy-paste error), screen readers announce the panel as closed even though content is visible. The fix is to ensure the button's collapsed class and aria-expanded attribute match the panel's show class on page load.
Q02 of 03SENIOR
When would you choose to implement accordion open/close behaviour with vanilla CSS :focus-within or the HTML details/summary element instead of Bootstrap's Collapse component — and what's the concrete trade-off in each case for a production FAQ page?
ANSWER
Use details/summary for simple, single-section toggles where you don't need mutual exclusivity (only one section open at a time). Bootstrap's Collapse is better for multi-panel accordions with data-bs-parent. :focus-within can simulate an accordion but loses state when focus moves — not suitable for content that needs to stay open while the user interacts inside it (e.g., forms). Trade-offs: details/summary is zero-JS and SEO-friendly (Google indexes content inside details) but no built-in mutual exclusivity and limited animation control. Bootstrap gives you animated, accessible, keyboard-navigable panels with minimal custom code but adds a dependency and initialisation overhead. For a production FAQ page with 10+ topics, Bootstrap is the pragmatic choice because users expect only one answer visible at a time, and accessibility is baked in.
Q03 of 03SENIOR
If you fire `new bootstrap.Collapse(element).show()` on a panel that Bootstrap is already managing via data attributes, what specific event is fired twice and what is the observable symptom in the UI — and how do you prevent it?
ANSWER
The event show.bs.collapse fires twice. First when the constructor (new bootstrap.Collapse(element)) is called (because it toggles the panel by default), and again when .show() is called. The observable symptom is the panel opening then immediately closing (or closing then opening, depending on initial state). This creates a visible flicker. Prevention: always pass { toggle: false } in the constructor options, or use bootstrap.Collapse.getOrCreateInstance(element, { toggle: false }).show() which avoids duplicate instances.
01
Bootstrap's accordion relies on the 'collapsed' CSS class to manage icon state. What happens if a developer initialises an accordion panel as open in the HTML but forgets to remove the 'collapsed' class from the trigger button — and how does this affect both the visual icon and screen reader behaviour?
SENIOR
02
When would you choose to implement accordion open/close behaviour with vanilla CSS :focus-within or the HTML details/summary element instead of Bootstrap's Collapse component — and what's the concrete trade-off in each case for a production FAQ page?
SENIOR
03
If you fire `new bootstrap.Collapse(element).show()` on a panel that Bootstrap is already managing via data attributes, what specific event is fired twice and what is the observable symptom in the UI — and how do you prevent it?
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
How do I add a plus and minus icon to a Bootstrap 5 accordion without JavaScript?
Use CSS pseudo-elements on .accordion-button::before. Set content: '+' as the default, then override with content: '\2212' (Unicode minus) on .accordion-button:not(.collapsed)::before. Bootstrap automatically adds and removes the collapsed class on the button as panels open and close — your CSS reacts to that class change. No event listeners needed.
Was this helpful?
02
What's the difference between the 'show' class and the 'collapsed' class in Bootstrap accordion?
They live on different elements and serve different purposes. The show class goes on the accordion-collapse div (the panel body) and controls whether the panel content is visible. The collapsed class goes on the accordion-button (the trigger) and signals that the button's associated panel is currently closed. They're managed together by Bootstrap, but they're on separate HTML elements — confusing them is the number one cause of icon-state bugs.
Was this helpful?
03
How do I open a specific Bootstrap accordion panel automatically on page load using JavaScript?
Grab the panel element by ID, construct a Collapse instance with { toggle: false } to prevent the constructor from toggling state, then call .show(). Full pattern: const panel = document.getElementById('collapseWarranty'); new bootstrap.Collapse(panel, { toggle: false }).show();. Run this inside a DOMContentLoaded listener to ensure Bootstrap has initialised. If you want URL-hash-based auto-open, read window.location.hash and target that element ID.
Was this helpful?
04
Why does my Bootstrap accordion open multiple panels at the same time instead of closing the previous one?
Every accordion-collapse div needs data-bs-parent pointing to the wrapper accordion's ID — not just the first one. Bootstrap's mutual-exclusivity logic only kicks in for panels that carry this attribute. If you added a new panel to an existing accordion and didn't include data-bs-parent, that new panel will open independently alongside others. Check every single accordion-collapse element in your markup — it's almost always a copy-paste omission on a later-added panel.
Was this helpful?
05
Can I use Font Awesome or SVG icons for the plus/minus instead of Unicode characters?
Yes. For Font Awesome, use .accordion-button::before { font-family: 'Font Awesome 6 Free'; content: '\f067'; } for plus (fa-plus) and override with content: '\f068'; for minus (fa-minus). For SVG, it's easier to use JavaScript to toggle a class that changes the inner SVG, because CSS content only works with text/characters, not external images. The JavaScript approach gives you full control over icon libraries.