Navigation Menu

Dropdown used in navigation with multiple items

Animation

Comparison of dropdown menu without animation and with CSS Grid animation

Without Animation

Menu shows/hides instantly

Dropdown (animations default)

Smooth animation using grid-template-rows is default

Custom Animation Speed

Animation speed can be set using CSS custom properties (variables). You can set them globally in CSS or inline using the style attribute.

Slow Animation (0.5s)

Fast Animation (0.1s)

Custom Easing (bounce)

Simple Examples

Basic Dropdown

Dialog Pattern (Playlist)

Uses data-dropdown-role="dialog" for non-menu content. Ideal for media players, complex controls, or interactive widgets.

Now Playing

Language Switcher (Navigation Pattern)

Uses data-dropdown-role="navigation" — žiadne role="menu", zachováva natívnu <nav> landmark a sémantiku <a> odkazov.

Right Aligned

Custom Behavior

Usage

When to Use Each ARIA Pattern

✅ Use Menu Pattern (default)

role="menu" + aria-haspopup="true"

  • Action menus (Edit, Delete, etc.)
  • User profile menus (button items)
  • Context menus (button items)

✅ Use Navigation Pattern

no role override + no aria-haspopup

  • Language/region switchers
  • Navigation dropdowns with links
  • Any dropdown containing <a> links
  • Use <nav> as menu element

✅ Use Dialog Pattern

role="dialog" + aria-haspopup="dialog"

  • Media player playlists/controls
  • Complex interactive widgets
  • Custom form controls
  • Rich content with multiple focusable elements

💡 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.

Basic HTML Structure (without animation):

<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>

Dialog Pattern (for non-menu content):

<!-- 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)
-->

Navigation Pattern (for link-based dropdowns):

<!-- 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 (default):

<!-- 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

Setting Animation Speed:

<!-- 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>

JavaScript Initialization:

1. ES6 Module Import - init functions (Recommended)

import { initDropdowns } from './a11y-kit/a11y-dropdown.js';

// Initialize after DOM is ready
document.addEventListener('DOMContentLoaded', () => {
  initDropdowns();
});

2. ES6 Module Import - Bootstrap style (short aliases)

// 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)
});

3. NPM package (Module bundlers: Astro, Vite, Webpack)

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);
});

Keyboard Navigation

Menu Pattern (default):

  • Enter / Space - Open/close menu or select item
  • / - Navigate between items
  • Home / End - Jump to first/last item
  • Esc - Close menu and return focus to button
  • Tab - Close menu and move focus

Navigation Pattern (data-dropdown-role="navigation"):

  • Enter / Space - Open/close or follow link
  • / - Navigate between links
  • Home / End - Jump to first/last link
  • Esc - Close and return focus to button
  • Tab - Close and move focus

Dialog Pattern:

  • Enter / Space - Open/close or select item
  • / - Navigate between items
  • Esc - Close and return focus to button
  • Tab - Move focus within dialog (does not close)

Features