
Collapsable.js is a vanilla JavaScript library for creating expandable HTML content panels and accordions.
It works with existing HTML and supports independent toggles, single-open groups, and collapsible sections that activate at specific screen widths.
Features
- Open a specific panel on page load through a CSS class or URL fragment.
- Keep at least one panel expanded when required.
- Toggle panels from external links or buttons.
- Configure responsive activation with CSS media queries.
- Cancel an opening or closing action through a DOM event.
- Customize the CSS classes used for open and closed states.
- Update ARIA attributes on interactive controls and content panels.
- Receive completion events after CSS animations finish.
How To Use It
Installation
Load the latest Collapsable.js in the document. You can write your own CSS styles for the panel controls, state indicators, and transitions.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/Collapsable.min.js"></script>
npm and ES Modules
For a project with a JavaScript bundler, install the package and import its default export.
npm install collapsable.js
import Collapsable from 'collapsable.js';
Basic Usage
Each collapsible item requires an HTML root, a control element, and a content box.
The default control and box classes are .js-collapsable__control and .js-collapsable__box. An explicit <button> makes the control keyboard-operable through native button behavior. If a control has no button or anchor, Collapsable.js creates a button for it.
<div id="shipping" class="js-collapsable">
<h3>
<button type="button" class="js-collapsable__control">
Shipping information
</button>
</h3>
<div class="js-collapsable__box">
<p>Orders usually ship within two business days.</p>
</div>
</div>
<div id="returns" class="js-collapsable">
<h3>
<button type="button" class="js-collapsable__control">
Returns and refunds
</button>
</h3>
<div class="js-collapsable__box">
<p>Contact support within 30 days of delivery.</p>
</div>
</div>
Initialize the library after the relevant markup is available. The Collapsable constructor accepts one HTMLElement or a NodeList of elements, followed by an optional configuration object.
const panels = new Collapsable(
document.querySelectorAll('.js-collapsable')
);
Single-Open Accordions
Set accordion: true to close the previous panel when another opens. For a group that must always have one expanded item, also set collapsableAll: false.
The js-collapsable--default-expanded class determines which item opens initially. When no item is marked and all panels cannot close, the first item opens.
<div id="account" class="js-collapsable js-collapsable--default-expanded"> <h3><button type="button" class="js-collapsable__control">Account</button></h3> <div class="js-collapsable__box"><p>Account settings.</p></div> </div> <div id="privacy" class="js-collapsable"> <h3><button type="button" class="js-collapsable__control">Privacy</button></h3> <div class="js-collapsable__box"><p>Privacy preferences.</p></div> </div>
const accordion = new Collapsable(
document.querySelectorAll('#account, #privacy'),
{ accordion: true, collapsableAll: false }
);
External Controls
External anchors use a URL fragment that matches the root item’s id. External buttons use data-collapsable-id. Insert these elements before the initialization script in Basic Usage. The default selector is .js-collapsable-ext-link. The following controls work with the shipping and returns panels from Basic Usage.
The data-collapsable-prevent-default attribute stops an external anchor from changing the browser’s location when clicked. Use the externalLinks.preventDefault option if this behavior should apply to every external control.
<a href="#shipping" class="js-collapsable-ext-link"
data-collapsable-prevent-default>
Toggle shipping information
</a>
<button type="button" class="js-collapsable-ext-link"
data-collapsable-id="returns">
Toggle returns
</button>
Responsive Activation
A panel with data-collapsable-media-query initializes its toggle behavior only while the media query matches. For mobile navigation, show the content normally on larger screens and conceal the unused control there. The example below operates as a collapsible section below 768px.
<div id="mobile-nav" class="js-collapsable"
data-collapsable-media-query="(max-width: 767px)">
<h3><button type="button" class="js-collapsable__control">Categories</button></h3>
<div class="js-collapsable__box">
<a href="/products/">Products</a>
<a href="/support/">Support</a>
</div>
</div>
@media (min-width: 768px) {
#mobile-nav .js-collapsable__control {
display: none;
}
}
new Collapsable(document.getElementById('mobile-nav'));
All Configuration Options
control(string, default'.js-collapsable__control:not(:scope .js-collapsable .js-collapsable__control)'): Selects the controls inside an item.box(string, default'.js-collapsable__box:not(:scope .js-collapsable .js-collapsable__box)'): Selects the item’s content boxes.event(string, default'click'): DOM event used to toggle a control.preventDefault(boolean, defaulttrue): Cancels the control event’s default browser action.eventDelayTimeout(number | undefined, defaultundefined): Maximum delay in milliseconds for a completion event while animations run. An undefined value waits for the tracked animations to finish.eventDelayGetAnimationsWithSubtree(boolean, defaultfalse): Includes animations on descendants when checking for completion.accordion(boolean, defaultfalse): Permits a maximum of one expanded item in a group.collapsableAll(boolean, defaulttrue): Permits every item to close. Set tofalseto keep at least one open.externalLinks.selector(string, default'.js-collapsable-ext-link'): Finds external anchors or buttons. An empty string disables external control discovery.externalLinks.preventDefault(boolean, defaultfalse): Cancels the default action for external control clicks.classNames.interactiveElement(string, default'js-collapsable__button'): Class applied to a control’s interactive button or anchor.classNames.expanded(string, default'js-collapsable--expanded'): Class applied to an expanded item root.classNames.collapsed(string, default'js-collapsable--collapsed'): Class applied to a collapsed item root.classNames.defaultExpanded(string, default'js-collapsable--default-expanded'): Class indicating that an item should open on initialization.classNames.externalLinkActive(string, default'js-collapsable-ext-link--active'): Class applied to external controls associated with an open item.
HTML Classes and Data Attributes
| Class | Purpose |
|---|---|
.js-collapsable | Root class used to identify nested collapsible items in the default selectors. |
.js-collapsable__control | Element containing the interactive toggle control. |
.js-collapsable__box | Collapsible content box. |
.js-collapsable__button | Class applied to the interactive button or anchor. |
.js-collapsable--default-expanded | Marks a panel as initially open. |
.js-collapsable--expanded / .js-collapsable--collapsed | State classes applied to item roots. |
.js-collapsable-ext-link | Marks an external toggle control. |
.js-collapsable-ext-link--active | Marks an external control associated with an open item. |
The following HTML attributes configure responsive behavior, default-open behavior, and external controls. During state changes, Collapsable.js manages hidden and aria-hidden on content boxes and aria-expanded and aria-controls on interactive controls. External elements with role="tab" receive aria-selected, including buttons with that role. This does not implement the complete keyboard interaction model for a tab widget.
| Attribute | Purpose |
|---|---|
data-collapsable-media-query | Activates the item when its CSS media query matches. |
data-collapsable-default-expanded-media | Opens the item initially when its CSS media query matches, provided no default-expanded class is set. |
data-collapsable-id | Connects an external button to an item root ID. |
data-collapsable-prevent-default | Cancels the default action on an individual external control when the attribute is present. |
Public Methods and Item API
const instance = new Collapsable(
document.querySelectorAll('.js-collapsable')
);
instance.expandAll();
instance.collapseAll();
const shipping = instance.getItemById('shipping');
shipping?.expand(new Event('programmatic'), null, false);
console.log(instance.getExpanded());
instance.destroy();
| Method | Description |
|---|---|
expandAll(data?) | Attempts to open the group’s items. In accordion mode, opens at most one. The optional data is included in event detail. Returns void. |
collapseAll(data?) | Attempts to close all open items, subject to collapsableAll. Returns void. |
getExpanded() | Returns an array of open CollapsableItem objects whose media queries currently match. |
getItemById(id?) | Returns the item with the specified root ID, or undefined. |
getExtLinkById(id?) | Returns the external control objects associated with an item ID. |
handleDefaultExpanded(media?) | Reapplies default-open processing, optionally for a MediaQueryList. This is also called during responsive initialization. Returns void. |
destroy() | Removes the group’s item and external-control listeners. Returns void. |
Group and Item Members
A Collapsable instance exposes the read-only options configuration and items array. getItemById() returns a CollapsableItem, also available as element.collapsableItem on an initialized item root. The properties and operations below are exposed on each item.
| Item member | Description |
|---|---|
expand(event, data, force) | Attempts to open the item. Returns boolean. Setting force to true bypasses cancelation of the opening event. |
collapse(event, data, force) | Attempts to close the item. Returns boolean. The last-open restriction applies. |
destroy() | Removes the item’s own event handlers and tracked state. Returns void. |
isExpanded | Read-only current expanded state (boolean). |
isDefaultExpanded | Read-only result of the initial-state rules (boolean). |
id | Read-only item root ID (string). |
element | Read-only root HTML element. |
controlElements | Read-only array of the item’s control elements. |
controlInteractiveElements | Read-only array of its interactive buttons and anchors. |
boxElements | Read-only array of associated content boxes. |
media | Read-only MediaQueryList or null for unrestricted items. |
collapsable | Read-only parent Collapsable instance. |
Events
const shippingPanel = document.getElementById('shipping');
shippingPanel.addEventListener('expand.collapsable', (event) => {
if (shippingPanel.dataset.locked === 'true') {
event.preventDefault();
}
});
shippingPanel.addEventListener('expanded.collapsable', (event) => {
console.log('Trigger:', event.detail.collapsableEvent?.type);
console.log('Data:', event.detail.data);
});
| Event | When it fires |
|---|---|
expand.collapsable | Immediately before an item opens. Cancelable. |
expanded.collapsable | After an item finishes opening. |
collapse.collapsable | Immediately before an item closes. Cancelable. |
collapsed.collapsable | After an item finishes closing. |
destroy.collapsable | When an item is destroyed. |
Styling and Customization
Collapsable.js does not include a required CSS theme. The library applies state classes and updates the content’s hidden attribute, so you can style arrows, borders, colors, and transitions to match your page.
.js-collapsable__control {
cursor: pointer;
}
.js-collapsable__control::after {
content: ' +';
}
.js-collapsable--expanded .js-collapsable__control::after {
content: ' −';
}
Alternatives and Related Resources
- Simple Plain Accordion Interface In Pure JS/CSS – Accordion.js
- Accessible Accordion With JavaScript And CSS3 – Accordion.js
- Accessible Animated Vanilla JS Accordion Library – rt-accordion
- Small Fast Accordion Plugin In Pure JavaScript – CFAccordion
- Lightweight jQuery Collapse Control Plugin – Collapsable







