Web

Fix Bootstrap Collapse Stuck in Collapsing State

Troubleshoot and fix Bootstrap collapse stuck in 'collapsing' state, failing to hide. Covers duplicate JS, position absolute, floats, event handlers, and initialization issues for reliable Bootstrap navbar collapse and Bootstrap 3 collapse fixes.

1 answer 1 view

Bootstrap Collapse element stuck in “collapsing” state and fails to retract

I’m using Bootstrap’s collapse component, but it gets stuck in the “collapsing” state when trying to hide it. Instead of fully collapsing, it re-adds the “show” class to the element.

Reproduction

The collapse animates open correctly but hangs during the closing animation and snaps back to visible.

HTML Structure

html
<!-- Button to trigger collapse element -->
<button 
 id="hrLangSwitchBtn" 
 type="button" 
 class="header-lang-switch-btn bg-gradient btn btn-lg rounded-5 fs-4 shadow-sm d-flex flex-row align-items-center gap-1" 
 role="button" 
 title="Change Site Language" 
 data-bs-toggle="collapse" 
 data-bs-target="#headerLangOptionsCollapse" 
 aria-expanded="false" 
 aria-controls="headerLangOptionsCollapse">
 <i class="bi bi-translate text-black fw-bold"></i>
</button>

<!-- Actual collapse element -->
<div class="collapse position-absolute" id="headerLangOptionsCollapse">
 <div class="card card-body rounded-5 bg-gradient p-0">
 <div class="d-flex flex-row align-items-center justify-content-center gap-2 w-100 rounded-pill p-1">
 <button class="btn btn-sm btn-secondary-subtle flex-grow-1 fs-5 fw-medium rounded-pill text-black d-flex p-0 gap-2 flex-row align-items-center justify-content-center">
 <component-lazy-image class="img-fluid"
 src="../assets/images/static/svg/flags/uk.svg"
 alt="British Flag"
 width="32"
 height="32">
 </component-lazy-image> English
 </button>
 <button class="btn btn-sm btn-secondary-subtle flex-grow-1 fs-5 fw-medium rounded-pill text-black d-flex p-0 gap-2 flex-row align-items-center justify-content-center">
 <component-lazy-image class="img-fluid"
 src="../assets/images/static/svg/flags/tr.svg"
 alt="Turkish Flag"
 width="32"
 height="32">
 </component-lazy-image> Türkçe
 </button>
 </div>
 </div>
</div>

Additional Details

  • Tried initializing and controlling via JavaScript as per Bootstrap documentation, but the element still won’t hide properly.
  • Tech stack: Webpack, custom web components with Light DOM (no Shadow DOM), TypeScript.

What could be causing this issue? How can I fix the collapse to properly hide the element?

Your bootstrap collapse is getting stuck because the Collapse JS expects a reliable height transition and a clean event path; anything that prevents the transitionend event (floated or absolutely-positioned children, duplicate Bootstrap scripts/event handlers, prevented events, or dynamic content changes like lazy images) can leave the element in the “collapsing” state and cause the “show” class to be re-added. Start by checking for duplicate Bootstrap JS imports and any show/hide event handlers that call preventDefault; also avoid putting position: absolute or float on the collapse target (move positioning to an outer wrapper or clear floats), and initialize the component with toggle: false if you control it programmatically. Concrete fixes and diagnostics below, with code and quick checks you can run right now.


Contents


Quick checklist

  • Look for duplicate Bootstrap JS files in your build (Webpack can bundle it twice).
  • Inspect event listeners on the collapse target for handlers that call e.preventDefault().
  • Remove or move position-absolute off the .collapse element.
  • Check for floated children inside the collapse target (clear them or wrap them).
  • Ensure lazy/async content (images/components) has intrinsic size or is loaded before toggling.
  • Initialize with toggle: false or use getOrCreateInstance to avoid first-call state bugs.

Diagnosing bootstrap collapse stuck “collapsing”

Why does Bootstrap hang in “collapsing”? Under the hood, the collapse script animates height from a measured value to 0 and waits for the CSS transitionend event to finish the work and swap classes. If that transition never fires (or another script re-adds classes while it’s mid-animation), the element can remain stuck with the .collapsing class.

Where that can break in your stack:

  • Duplicate or conflicting Bootstrap JS versions cause multiple handlers to fight each other (see reproduction reports). Check your final bundle and loaded scripts for more than one bootstrap file—Webpack vendors can be culprits (example report).
  • Global handlers that intercept show/hide events and call preventDefault or return false will block the normal hide flow (people have accidentally disabled hide by doing $('.collapse').on('show.bs.collapse hide.bs.collapse', e => e.preventDefault())) — inspect listeners in DevTools (example thread).
  • CSS/layout issues: floats or absolutely positioned children can make the parent’s measured height unstable; see the upstream GitHub issue about floats causing the “collapsing” class to stick (GitHub issue #9076).
  • Initialization/timing: calling hide/show before the component is properly initialized or before content measurements are stable can invert expected state—Bootstrap issues discuss first-call timing problems (issue #5859, SO threads).
  • Missing or overridden collapse transition CSS: if your CSS build strips Bootstrap’s transition rules the JS will wait for an event that never occurs. Confirm the collapse CSS is present (official docs).

How to inspect quickly:

  • List bootstrap scripts in the page:
js
[...document.querySelectorAll('script[src]')]
 .map(s => s.src).filter(Boolean)
 .filter(src => src.includes('bootstrap'))
  • Inspect event listeners: open Chrome DevTools → Elements → select the collapse element → Event Listeners panel. Look for show/hide handlers and the source file. Or in console (Chrome only) try getEventListeners(document.getElementById('headerLangOptionsCollapse')).
  • Check computed styles on the collapse element while it’s open and while it’s closing: look at transition-duration and overflow. If transition-duration is 0 or transition absent, the transitionend won’t fire.
  • Check children for float/absolute: select the collapse element in Elements and look for position/float in Computed panel.

Fixes: bootstrap collapse that won’t hide

Follow these fixes in order (fastest → deeper):

  1. Remove duplicate Bootstrap JS
  1. Look for event handlers that block hide
  1. Fix layout / float / absolute problems (very common)
  • Don’t put position: absolute or directly-floated elements on the collapse target. In your HTML you have class="collapse position-absolute" — remove position-absolute from the collapse element. If you need absolute positioning for the dropdown-like UI, place the collapse inside an absolutely-positioned wrapper OR absolutely-position an outer container and keep .collapse a normal flow element so scrollHeight is measured reliably.

Suggested markup pattern:

html
<div class="position-relative">
 <button ... data-bs-target="#headerLangOptionsCollapse">...</button>

 <div class="position-absolute" style="top:100%; right:0;">
 <!-- keep .collapse here so it can measure height normally -->
 <div class="collapse" id="headerLangOptionsCollapse">
 <div class="card card-body rounded-5 bg-gradient p-0"> ... </div>
 </div>
 </div>
</div>
  • If you must keep floats inside the collapse, make the collapse contain them (clearfix/overflow):
css
#headerLangOptionsCollapse::after { content: ""; display: table; clear: both; }
#headerLangOptionsCollapse { overflow: auto; }

See the float bug discussion: https://github.com/twbs/bootstrap/issues/9076

  1. Ensure the collapse CSS and transitions are present
  1. Handle lazy/async content
  • Your custom might insert images after the collapse opens or while it’s closing. Give images intrinsic width/height, use placeholders, or delay toggling until images are in place. This prevents mid-animation height jumps that can confuse transition tracking.
  1. Initialization / timing: use toggle:false
  • If you control the collapse via JS, initialize it safely:
js
const el = document.getElementById('headerLangOptionsCollapse');
const bs = bootstrap.Collapse.getOrCreateInstance(el, { toggle: false });
// later
bs.hide();

Sometimes calling hide/show before initialization leads to inverted behavior; use toggle: false or call initialization after DOMContentLoaded. See similar fixes: https://stackoverflow.com/questions/17750907/bootstrap-collapse-doesnt-toggle-after-you-show-hide-or-toggle-from-code

  1. Fallback guard (last resort)
  • If you can’t find the root cause quickly, attach a short fallback that detects a stuck state and finishes it. Only use this as a safety net:
js
el.addEventListener('hide.bs.collapse', () => {
 const t = setTimeout(() => {
 if (el.classList.contains('collapsing')) {
 el.classList.remove('collapsing');
 el.classList.remove('show');
 el.classList.add('collapse');
 el.style.height = '';
 }
 }, 600); // match your transition duration + margin
 el.addEventListener('transitionend', () => clearTimeout(t), { once: true });
});

This forces a clean state if a transitionend is lost — but it’s better to fix the underlying cause.


Bootstrap 3 collapse and version traps

If any legacy code or CSS is present, confirm you’re not mixing Bootstrap 3/4/5 assets. Version mismatches often produce different data-attribute names (data-toggle vs data-bs-toggle) or incompatible JS behavior. If you have older components, update attributes and imports or isolate legacy code to avoid conflicts. Some older threads show issues where the first programmatic call behaved incorrectly; initialize with toggle=false as above to avoid that class-of-problems (see issue discussion: https://github.com/twbs/bootstrap/issues/5859).


Init and timing (TypeScript / Webpack notes)

  • In TypeScript + Webpack projects import Bootstrap once (prefer the bundle): import 'bootstrap/dist/js/bootstrap.bundle.min.js'; and avoid mixing CDN and local builds.
  • Initialize after DOM ready:
ts
document.addEventListener('DOMContentLoaded', () => {
 const el = document.getElementById('headerLangOptionsCollapse')!;
 bootstrap.Collapse.getOrCreateInstance(el, { toggle: false });
});
  • If controlling the collapse from inside custom elements, ensure the element is light-DOM (you said it is) and the collapse target is present in the final DOM when you call getOrCreateInstance.

Sources


Conclusion

The most likely culprits for your stuck bootstrap collapse are duplicate/conflicting Bootstrap JS, an event handler preventing hide, or CSS/layout issues (floats / position: absolute / dynamic content) that prevent the transitionend event. Start by removing position-absolute from the .collapse target (or move positioning to an outer wrapper), confirm only one Bootstrap script is loaded, inspect listeners for preventDefault, and initialize with toggle: false using getOrCreateInstance. Fix those, and your bootstrap collapse should stop getting stuck and hide reliably.

Authors
Verified by moderation
Moderation
Fix Bootstrap Collapse Stuck in Collapsing State