accordionary
v1.0.4
Published
A lightweight, accessible, vanilla JavaScript accordion with zero dependencies
Maintainers
Readme
accordionary
A lightweight, accessible, vanilla JavaScript accordion with zero dependencies. Perfect for Webflow, static sites, or anywhere you need a simple accordion.
Features
- Zero dependencies
- ARIA compliant
- Keyboard navigation (Enter/Space)
- Smooth, customizable animations
- Respects
prefers-reduced-motion - Configurable via HTML attributes
- Programmatic API for full control
- Generate accordions from JSON data (package manager only)
- TypeScript support
- ~4KB minified
Installation
Browser (Auto-Initialize)
Download dist/accordionary.js and include it in your HTML:
<script src="your-url/accordionary.js"></script>Or link directly from GitHub (replace v1.0.2 with the desired version):
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/accordionary.js"></script>This version auto-initializes all accordions on page load.
npm / bun / etc
npm install accordionarybun install accordionaryimport Accordionary from "accordionary";
// Initialize a single accordion
const accordion = Accordionary.init("#my-accordion");
// Or initialize all accordions on the page
const accordions = Accordionary.initAll();Usage
Basic Structure
<div accordionary="component">
<div accordionary="item">
<div accordionary="header">
<span>Section Title</span>
<span accordionary="icon">▼</span>
</div>
<div accordionary="content">
<p>Your content here.</p>
</div>
</div>
<!-- More items... -->
</div>Minimal CSS
Add some basic styles to make it look nice:
[accordionary="component"] {
border: 1px solid #ddd;
border-radius: 4px;
}
[accordionary="item"] {
border-bottom: 1px solid #ddd;
}
[accordionary="item"]:last-child {
border-bottom: none;
}
[accordionary="header"] {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f5f5f5;
}
[accordionary="header"]:hover {
background: #eee;
}
[accordionary="header"]:focus {
outline: 2px solid #0066cc;
outline-offset: -2px;
}
[accordionary="content"] {
padding: 0 1rem;
}
[accordionary="content"] > * {
padding: 1rem 0;
}Configuration
All configuration is done via HTML attributes. No JavaScript required.
Component-Level Attributes
| Attribute | Values | Default | Description |
| ----------------------- | ---------------------- | ------- | ----------------------------------- |
| accordionary-open | all, first, none | none | Which items to open by default |
| accordionary-multiple | true, false | true | Allow multiple items open at once |
| accordionary-speed | number (ms) | 300 | Animation duration in milliseconds |
| accordionary-easing | CSS easing | ease | Animation easing function |
| accordionary-link | true, false | false | Link all items: open/close together |
Item-Level Attributes
| Attribute | Values | Default | Description |
| ---------------------- | --------------- | ---------- | ----------------------------------- |
| accordionary-open | true, false | (inherits) | Override component-level open state |
| accordionary-disable | true, false | false | Prevent toggling, hide icon |
Examples
Open First Item by Default
<div accordionary="component" accordionary-open="first">
<!-- items -->
</div>Open All Items by Default
<div accordionary="component" accordionary-open="all">
<!-- items -->
</div>Single Open Only (Classic Accordion)
<div accordionary="component" accordionary-multiple="false" accordionary-open="first">
<!-- items -->
</div>Linked Items (Open/Close Together)
<div accordionary="component" accordionary-link="true">
<!-- Clicking any item open opens all; clicking any item closed closes all -->
</div>Disabled items are excluded from linking — they remain in their current state.
Force Specific Item Open
<div accordionary="component">
<div accordionary="item" accordionary-open="true">
<!-- This item starts open -->
</div>
<div accordionary="item">
<!-- This item starts closed -->
</div>
</div>Always-Open Disabled Item
<div accordionary="component" accordionary-open="all">
<div accordionary="item" accordionary-disable="true">
<!-- Cannot be closed, icon hidden -->
</div>
</div>Custom Animation
<div accordionary="component" accordionary-speed="500" accordionary-easing="ease-in-out">
<!-- Slower animation with ease-in-out -->
</div>Custom Open/Close Icons (Swap Mode)
Instead of rotating a single icon, you can provide separate icons for the open and closed states. Add accordionary="icon-open" and accordionary="icon-close" elements inside the icon container:
<div accordionary="component">
<div accordionary="item">
<div accordionary="header">
<span>Section Title</span>
<span accordionary="icon">
<span accordionary="icon-open">−</span>
<span accordionary="icon-close">+</span>
</span>
</div>
<div accordionary="content">
<p>Your content here.</p>
</div>
</div>
</div>When icon-open and icon-close children are present, the library toggles their visibility instead of rotating the icon. You can use any HTML — text, SVGs, images, etc.:
<span accordionary="icon">
<span accordionary="icon-open">
<svg><!-- minus icon --></svg>
</span>
<span accordionary="icon-close">
<svg><!-- plus icon --></svg>
</span>
</span>If neither icon-open nor icon-close is present, the default rotation behavior applies.
Programmatic API
When installed via package manager, you get full programmatic control over accordions.
Initialization
import Accordionary from "accordionary";
// Initialize by selector
const accordion = Accordionary.init("#my-accordion");
// Initialize by element
const element = document.querySelector("#my-accordion");
const accordion = Accordionary.init(element);
// Initialize all accordions on page
const accordions = Accordionary.initAll();Accordion Controller
The init() function returns an AccordionController with these methods:
const accordion = Accordionary.init("#my-accordion");
// Open/close all items
accordion.openAll();
accordion.closeAll();
// Control specific items by index
accordion.open(0); // Open first item
accordion.close(1); // Close second item
accordion.toggle(2); // Toggle third item
// Access individual item controllers
accordion.items[0].open();
accordion.items[0].close();
accordion.items[0].toggle();
// Access the DOM element
accordion.element; // HTMLElementTypeScript Support
Full TypeScript definitions are included:
import Accordionary, { type AccordionController, type ItemController } from "accordionary";
const accordion: AccordionController | null = Accordionary.init("#my-accordion");Generating Accordions from JSON
When installed via package manager, you can generate accordion HTML from structured JSON data using the generateAccordionary function.
Basic Usage
import { generateAccordionary } from "accordionary";
const data = {
items: [
{
heading: "Question 1",
content: "Answer 1",
},
{
heading: "Question 2",
content: "Answer 2",
},
],
};
// Generate the accordion element
const element = generateAccordionary(data);
// Append to DOM
document.body.appendChild(element);
// Initialize it
const accordion = Accordionary.init(element);Configuration Options
const element = generateAccordionary(data, {
icon: "▼", // HTML string for icon in rotate mode (default: "▼")
iconOpen: undefined, // HTML string for open-state icon (swap mode)
iconClose: undefined, // HTML string for closed-state icon (swap mode)
openDefault: "none", // "all" | "first" | "none" (default: "none")
allowMultiple: true, // Allow multiple items open (default: true)
speed: 300, // Animation duration in ms (default: 300)
easing: "ease", // CSS easing function (default: "ease")
linked: false, // Link all items open/close together (default: false)
classes: {
component: ["my-accordion"], // Custom classes for component
item: ["my-item"], // Custom classes for items
heading: ["my-heading"], // Custom classes for headings
content: ["my-content"], // Custom classes for content
icon: ["my-icon"], // Custom classes for icons
},
});When both iconOpen and iconClose are provided, the generator creates swap mode icons (child elements inside the icon container). When omitted, the icon option is used with the default rotate behavior.
Custom Icon
You can provide any HTML string for the icon - emoji, SVG, or image tag:
// Emoji
const element = generateAccordionary(data, {
icon: "👇",
});
// SVG
const element = generateAccordionary(data, {
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6l-6-6z"/>
</svg>`,
});
// Image
const element = generateAccordionary(data, {
icon: '<img src="/chevron.svg" alt="">',
});Swap Mode Icons (Generator)
Use iconOpen and iconClose to generate separate open/closed state icons instead of a rotating icon:
// Plus/minus text
const element = generateAccordionary(data, {
iconOpen: "−",
iconClose: "+",
});
// SVG icons
const element = generateAccordionary(data, {
iconOpen: "<svg><!-- minus SVG --></svg>",
iconClose: "<svg><!-- plus SVG --></svg>",
});Item-Level Configuration
Individual items can have their own configuration:
const data = {
items: [
{
heading: "Normal Item",
content: "This item follows component defaults",
},
{
heading: "Force Open Item",
content: "This item starts open regardless of component settings",
config: {
openOverride: true,
},
},
{
heading: "Disabled Item",
content: "This item cannot be toggled and is always visible",
config: {
disabled: true,
},
},
],
};HTML in Content
Both heading and content accept HTML strings:
const data = {
items: [
{
heading: '<div class="font-bold">Rich <em>Heading</em></div>',
content: `
<img src="/image.jpg" alt="Description">
<p>Paragraph with <strong>bold text</strong></p>
<ul>
<li>List item 1</li>
<li>List item 2</li>
</ul>
`,
},
],
};TypeScript Support
Full type definitions are included:
import { generateAccordionary, type AccordionData, type GeneratorConfig } from "accordionary";
const data: AccordionData = {
items: [
{
heading: "Question",
content: "Answer",
},
],
};
const config: GeneratorConfig = {
openDefault: "first",
classes: {
component: ["custom-accordion"],
},
};
const element = generateAccordionary(data, config);Accessibility
Accordionary is built with accessibility in mind:
- Headers have
role="button"and are keyboard focusable aria-expandedindicates open/closed statearia-controlslinks headers to content panels- Content panels have
role="region"andaria-labelledby - Full keyboard support (Enter and Space to toggle)
- Disabled items have
aria-disabled="true" - Respects
prefers-reduced-motion(disables animations automatically)
Browser Support
Works in all modern browsers (Chrome, Firefox, Safari, Edge).
License
GPL-3.0-or-later
