Fully accessible dropdown with keyboard navigation and ARIA support
Dropdown used in navigation with multiple items
Comparison of dropdown menu without animation and with CSS Grid animation
Menu shows/hides instantly
Smooth animation using grid-template-rows is default
Animation speed can be set using CSS custom properties (variables).
You can set them globally in CSS or inline using the style attribute.
Uses data-dropdown-role="dialog" for non-menu content.
Ideal for media players, complex controls, or interactive widgets.
Uses data-dropdown-role="navigation" — žiadne role="menu",
zachováva natívnu <nav> landmark a sémantiku <a> odkazov.
role="menu" + aria-haspopup="true"
no role override + no aria-haspopup
<a> links<nav> as menu elementrole="dialog" + aria-haspopup="dialog"
💡 Navigation pattern zachováva natívnu sémantiku <nav> a odkazov. Dialog pattern cykluje Tab vnútri dropdownu. Menu pattern sa zatvára pri Tab.
<div data-dropdown>
<button data-dropdown-button>
Button Text
<span class="dropdown__arrow"></span>
</button>
<div data-dropdown-menu data-no-animation>
<div>
<div>
<a href="#" data-dropdown-item>Item 1</a>
<a href="#" data-dropdown-item>Item 2</a>
</div>
</div>
</div>
</div>
<!-- Use data-dropdown-role="dialog" for playlists, controls, or interactive widgets -->
<div data-dropdown data-dropdown-role="dialog">
<button data-dropdown-button>
🎵 Playlist
<span class="dropdown__arrow"></span>
</button>
<div data-dropdown-menu>
<div>
<div>
<button data-dropdown-item>Station 1</button>
<button data-dropdown-item>Station 2</button>
</div>
</div>
</div>
</div>
<!-- This sets:
- Button: aria-haspopup="dialog" (instead of "true")
- Menu: role="dialog" (instead of "menu")
- Items: No role attribute (instead of "menuitem")
- Keyboard: Tab allowed within dropdown (menu pattern closes on Tab)
-->
<!-- Use data-dropdown-role="navigation" for language switchers, nav menus with links -->
<div data-dropdown data-dropdown-role="navigation">
<button data-dropdown-button aria-label="Výber jazyka">
🇬🇧 English
<span class="dropdown__arrow"></span>
</button>
<nav data-dropdown-menu aria-label="Výber jazyka">
<div>
<div>
<ul style="list-style: none; margin: 0; padding: 0;">
<li>
<a href="/en" hreflang="en" lang="en" data-dropdown-item aria-current="page">English</a>
</li>
<li>
<a href="/sk" hreflang="sk" lang="sk" data-dropdown-item>Slovensky</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
<!-- This sets:
- Button: aria-expanded + aria-controls only (no aria-haspopup)
- Menu: no role override (preserves native <nav> landmark)
- Items: no role override (preserves native <a> link semantics)
- Keyboard: Arrow keys navigate, Tab closes (same as menu pattern)
-->
<!-- Animations are default, no need to add any attribute -->
<div data-dropdown>
<button data-dropdown-button>
Button Text
<span class="dropdown__arrow"></span>
</button>
<div data-dropdown-menu>
<!-- Two wrapper divs REQUIRED for animations -->
<div>
<div>
<a href="#" data-dropdown-item>Item 1</a>
<a href="#" data-dropdown-item>Item 2</a>
</div>
</div>
</div>
</div>
Important for animations:
• Animations are default - automatically enabled
• Content in data-dropdown-menu must be wrapped in two <div> wrappers
• Animation uses modern CSS Grid approach (grid-template-rows: 0fr → 1fr)
• Works without needing to know menu height beforehand
• Better alternative to transform and fixed height animations
• Animation speed: use CSS custom property --a11y-dropdown-duration (default: 0.2s)
• Easing function: use CSS custom property --a11y-dropdown-easing (default: ease)
• Automatic prefers-reduced-motion support
<!-- Inline using style attribute -->
<div data-dropdown style="--a11y-dropdown-duration: 0.5s;">
<button data-dropdown-button>Menu</button>
<div data-dropdown-menu>
<div><div>...</div></div>
</div>
</div>
<!-- Custom easing -->
<div data-dropdown
style="--a11y-dropdown-duration: 0.3s; --a11y-dropdown-easing: ease-in-out;">
<!-- dropdown menu -->
</div>
<!-- Or globally in CSS -->
<style>
:root {
--a11y-dropdown-duration: 0.25s;
--a11y-dropdown-easing: cubic-bezier(0.4, 0, 0.2, 1);
}
</style>
<!-- Disable animations -->
<div data-dropdown-menu data-no-animation>
<!-- menu without animations -->
</div>
import { initDropdowns } from './a11y-kit/a11y-dropdown.js';
// Initialize after DOM is ready
document.addEventListener('DOMContentLoaded', () => {
initDropdowns();
});
// Import short alias
import { Dropdown } from './a11y-kit/a11y-dropdown.js';
// Use like Bootstrap
const dropdown = new Dropdown(element, {
dropdownRole: 'menu', // 'menu' (default), 'dialog', 'listbox', or 'navigation'
closeOnSelect: true,
closeOnOutsideClick: true,
closeOnEscape: true,
onOpen: (instance) => console.log('Opened'),
onClose: (instance) => console.log('Closed'),
onSelect: (item, index) => console.log('Selected:', item)
});
npm install accessible-kit
// Import only what you need (best for tree-shaking)
import { initDropdowns, Dropdown } from 'accessible-kit/dropdown';
// Initialize after DOM is ready
document.addEventListener('DOMContentLoaded', () => {
initDropdowns(); // Auto-init all dropdowns
// OR create instances manually
const dropdown = new Dropdown(element, options);
});
data-dropdown-role="navigation"):data-dropdown-role attribute