Panel
A single element that opens and closes with a smooth transition.
What Panel does
Panel controls one element: it can be opened, closed, or toggled. The element animates its height (vertical axis) or width (horizontal axis) between 0 and its natural size.
Typical uses: show-more blocks, accordions, sidebars, drawers.
Not the same as PanelSet. Panel opens and closes a single element. PanelSet switches between several. See the Intro for the distinction.
Setup
Installing Panel
npm install panelset
Using Panel
Installing Panel
Download the ZIP from the releases page, unzip, and place the files somewhere your HTML can reach.
Using Panel
Installing Panel
No download needed. Files are served directly from jsDelivr.
https://cdn.jsdelivr.net/npm/panelset@latest/dist/
Using Panel
Markup
Add data-panel to your element and give it an id. Panels start closed by default (no class required). Wrap the content in .panel-wrapper.
Key Features
- This is our most important feature ever!
<!-- Trigger: any element with aria-controls pointing to the panel id-->
<button aria-controls="markup-example">Toggle</button>
<!-- Panel element-->
<div id="markup-example" data-panel>
<div class="panel-wrapper">
<p>Panel content goes here.</p>
</div>
</div>
aria-expanded is managed automatically. Panel sets it to true or false on every open and close.
Starting state
A Panel is closed by default. To start it open, add the is-open class to the panel element in your markup. That’s all. Panel reads it on init, removes inert, and syncs the trigger’s aria-expanded to match. No animation runs for the initial state.
Key Features
- This is our most important feature ever!
<button aria-controls="starting-example">Toggle</button>
<div class="is-open" id="starting-example" data-panel>
<div class="panel-wrapper">
<p>Visible on load.</p>
</div>
</div>
If you use persist or deepLink, a stored open/closed state takes precedence over the markup on return visits.
Initializing
Use the static init() method to initialize all matching elements at once, or create instances directly.
// Initialize all [data-panel] elements
Panel.init();
// With options
Panel.init({ debug: true });
// Custom selector
Panel.init('[data-panel]', { transitions: false });
// Single instance
const panel = new Panel('#my-panel', { axis: 'vertical' });
// Single instance by element reference
const el = document.getElementById('my-panel');
const panel = new Panel(el, { autoFocus: 'first' });
Triggers and close buttons
Any element with aria-controls="<panel id here>" auto-binds a click handler on init. No extra JS needed. Add data-panel-close inside the panel for a close button.
<button aria-controls="my-panel" aria-expanded="false">Open</button>
<div id="my-panel" data-panel>
<div class="panel-wrapper" role="dialog" aria-label="Options">
<button data-panel-close>Close</button>
<p>Sidebar content.</p>
</div>
</div>
When a trigger and panel are adjacent siblings, you can skip the id / aria-controls pair entirely. Use data-panel-trigger on the button and Panel will generate and set up the ID automatically.
<button data-panel-trigger>Toggle</button>
<div data-panel>
<div class="panel-wrapper">Content</div>
</div>
Async content
Attach a handler to delay opening until content is ready. If loading takes longer than loadingDelayms, a spinner appears.
const panel = new Panel('#my-panel');
panel.onBeforeOpen(async (el, signal) => {
const res = await fetch('/api/content', { signal });
el.querySelector('.panel-wrapper').innerHTML = await res.text();
}, { once: true }); // load once, cache forever