accessible-kit 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +68 -19
- package/package.json +1 -1
- package/src/js/a11y-dropdown.js +21 -9
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.9] - 2026-03-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Dropdown - Navigation ARIA Pattern**: New `data-dropdown-role="navigation"` value for disclosure dropdowns containing links (language switchers, nav menus)
|
|
12
|
+
- **Button**: sets only `aria-expanded` + `aria-controls` — no `aria-haspopup` (correct for disclosure pattern per WAI-ARIA APG)
|
|
13
|
+
- **Menu element**: no `role` override — preserves native `<nav>` landmark semantics
|
|
14
|
+
- **Items**: no `role` override — preserves native `<a>` link semantics and `hreflang`/`aria-current` attributes
|
|
15
|
+
- **Keyboard**: Arrow keys navigate, `Home`/`End` jump to first/last, `Tab` closes (same as menu pattern)
|
|
16
|
+
- **Mouse click**: links navigate naturally and support Ctrl+click / middle-click (open in new tab)
|
|
17
|
+
- Updated demo language switchers (header nav + standalone) to use the new pattern with `<nav>`, `<ul>/<li>/<a>`, `hreflang`, and `aria-current`
|
|
18
|
+
- Updated Usage info box in demo to 3-column grid: Menu / Navigation / Dialog patterns
|
|
19
|
+
- Backwards compatible — all existing `menu`, `dialog`, `listbox` dropdowns unchanged
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **README**: Updated ARIA Patterns section — removed "Language/region selectors" and "Navigation menus" from Menu Pattern (they were semantically incorrect there), added dedicated Navigation Pattern description and code example
|
|
23
|
+
- **README**: Updated Language Switcher variant example to use `data-dropdown-role="navigation"` with `<nav>` and `<a>` elements
|
|
24
|
+
- **README**: Added Navigation Pattern to Keyboard Navigation section
|
|
25
|
+
|
|
8
26
|
## [1.0.7] - 2026-01-26
|
|
9
27
|
|
|
10
28
|
### Changed
|
|
@@ -171,6 +189,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
171
189
|
- Zero dependencies
|
|
172
190
|
- Full TypeScript-ready exports
|
|
173
191
|
|
|
192
|
+
[1.0.9]: https://github.com/5ulo/accessible-kit/compare/v1.0.8...v1.0.9
|
|
193
|
+
[1.0.8]: https://github.com/5ulo/accessible-kit/compare/v1.0.7...v1.0.8
|
|
174
194
|
[1.0.7]: https://github.com/5ulo/accessible-kit/compare/v1.0.6...v1.0.7
|
|
175
195
|
[1.0.6]: https://github.com/5ulo/accessible-kit/compare/v1.0.5...v1.0.6
|
|
176
196
|
[1.0.5]: https://github.com/5ulo/accessible-kit/compare/v1.0.4...v1.0.5
|
package/README.md
CHANGED
|
@@ -173,7 +173,7 @@ Fully accessible dropdown component with keyboard navigation and ARIA support. S
|
|
|
173
173
|
|
|
174
174
|
### Features
|
|
175
175
|
|
|
176
|
-
- ✅ Full ARIA attributes support (menu, dialog, and listbox patterns)
|
|
176
|
+
- ✅ Full ARIA attributes support (menu, navigation, dialog, and listbox patterns)
|
|
177
177
|
- ✅ Flexible role patterns via `data-dropdown-role` attribute
|
|
178
178
|
- ✅ Keyboard navigation (arrows, Enter, Space, Esc, Home, End)
|
|
179
179
|
- ✅ Focus management
|
|
@@ -222,11 +222,14 @@ The dropdown component supports multiple ARIA patterns to match different use ca
|
|
|
222
222
|
#### When to Use Each Pattern
|
|
223
223
|
|
|
224
224
|
**Menu Pattern (default)** - Use `role="menu"` for:
|
|
225
|
-
- Navigation menus
|
|
226
225
|
- Action menus (Edit, Delete, etc.)
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
|
|
226
|
+
- User profile menus (button items)
|
|
227
|
+
- Context menus (button items)
|
|
228
|
+
|
|
229
|
+
**Navigation Pattern** - Use `data-dropdown-role="navigation"` for:
|
|
230
|
+
- Language/region switchers
|
|
231
|
+
- Navigation dropdowns containing `<a>` links
|
|
232
|
+
- Any disclosure widget where native `<nav>` landmark and link semantics must be preserved
|
|
230
233
|
|
|
231
234
|
**Dialog Pattern** - Use `data-dropdown-role="dialog"` for:
|
|
232
235
|
- Media player playlists/controls
|
|
@@ -266,6 +269,38 @@ The dropdown component supports multiple ARIA patterns to match different use ca
|
|
|
266
269
|
**Why use dialog pattern?**
|
|
267
270
|
When your dropdown contains complex interactive content (like a media player playlist), it's not semantically a "menu" of commands. The dialog pattern better represents this content structure and provides more appropriate keyboard behavior. Tab/Shift+Tab cycles through items within the dropdown (instead of closing it like menu pattern does).
|
|
268
271
|
|
|
272
|
+
**Example: Navigation Pattern (Language Switcher)**
|
|
273
|
+
|
|
274
|
+
```html
|
|
275
|
+
<!-- Use data-dropdown-role="navigation" for language switchers and nav menus with links -->
|
|
276
|
+
<div data-dropdown data-dropdown-role="navigation">
|
|
277
|
+
<button data-dropdown-button aria-label="Výber jazyka">
|
|
278
|
+
🇬🇧 English
|
|
279
|
+
<span data-dropdown-arrow></span>
|
|
280
|
+
</button>
|
|
281
|
+
<nav data-dropdown-menu aria-label="Výber jazyka">
|
|
282
|
+
<div>
|
|
283
|
+
<div>
|
|
284
|
+
<ul style="list-style: none; margin: 0; padding: 0;">
|
|
285
|
+
<li><a href="/en" hreflang="en" lang="en" data-dropdown-item aria-current="page">English</a></li>
|
|
286
|
+
<li><a href="/sk" hreflang="sk" lang="sk" data-dropdown-item>Slovensky</a></li>
|
|
287
|
+
</ul>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</nav>
|
|
291
|
+
</div>
|
|
292
|
+
|
|
293
|
+
<!-- This automatically sets:
|
|
294
|
+
- Button: aria-expanded + aria-controls only (no aria-haspopup)
|
|
295
|
+
- Menu: no role override (preserves native <nav> landmark)
|
|
296
|
+
- Items: no role override (preserves native <a> link semantics)
|
|
297
|
+
- Keyboard: Arrow keys navigate, Tab closes (same as menu pattern)
|
|
298
|
+
-->
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Why use navigation pattern?**
|
|
302
|
+
Language switchers and nav dropdowns contain `<a>` links, not menu commands. The `role="menu"` + `role="menuitem"` pattern is semantically incorrect for links — it hides the native link semantics from screen readers. The navigation pattern keeps the `<nav>` landmark and `<a>` link roles intact, allowing users to understand they're navigating, not executing commands.
|
|
303
|
+
|
|
269
304
|
### JavaScript Initialization
|
|
270
305
|
|
|
271
306
|
```javascript
|
|
@@ -278,7 +313,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
278
313
|
|
|
279
314
|
// Or manual initialization with options
|
|
280
315
|
const dropdown = new Dropdown(element, {
|
|
281
|
-
dropdownRole: 'menu', // 'menu' (default), 'dialog', or '
|
|
316
|
+
dropdownRole: 'menu', // 'menu' (default), 'dialog', 'listbox', or 'navigation'
|
|
282
317
|
closeOnSelect: true,
|
|
283
318
|
closeOnOutsideClick: true,
|
|
284
319
|
closeOnEscape: true,
|
|
@@ -340,7 +375,7 @@ Dropdowns have **CSS Grid animations enabled by default**. The animation automat
|
|
|
340
375
|
|
|
341
376
|
```javascript
|
|
342
377
|
{
|
|
343
|
-
dropdownRole: 'menu', // ARIA pattern: 'menu', 'dialog', or '
|
|
378
|
+
dropdownRole: 'menu', // ARIA pattern: 'menu', 'dialog', 'listbox', or 'navigation'
|
|
344
379
|
closeOnSelect: true, // Close after selecting item
|
|
345
380
|
closeOnOutsideClick: true, // Close on outside click
|
|
346
381
|
closeOnEscape: true, // Close on Escape key
|
|
@@ -368,6 +403,14 @@ Dropdowns have **CSS Grid animations enabled by default**. The animation automat
|
|
|
368
403
|
- **Esc** - Close and return focus to button
|
|
369
404
|
- **Tab** / **Shift+Tab** - Cycle through items within dialog (does not close)
|
|
370
405
|
|
|
406
|
+
**Navigation Pattern:**
|
|
407
|
+
|
|
408
|
+
- **↓** / **↑** - Navigate between links
|
|
409
|
+
- **Home** / **End** - Jump to first/last link
|
|
410
|
+
- **Esc** - Close and return focus to button
|
|
411
|
+
- **Tab** - Close and move focus
|
|
412
|
+
- **Enter** - Follow link (native browser behavior)
|
|
413
|
+
|
|
371
414
|
### Variants
|
|
372
415
|
|
|
373
416
|
**Navigation menu:**
|
|
@@ -389,25 +432,31 @@ Dropdowns have **CSS Grid animations enabled by default**. The animation automat
|
|
|
389
432
|
**Language switcher:**
|
|
390
433
|
|
|
391
434
|
```html
|
|
392
|
-
<div data-dropdown data-variant="language">
|
|
393
|
-
<button data-dropdown-button>
|
|
435
|
+
<div data-dropdown data-variant="language" data-dropdown-role="navigation">
|
|
436
|
+
<button data-dropdown-button aria-label="Výber jazyka">
|
|
394
437
|
🇬🇧 EN
|
|
395
438
|
<span data-dropdown-arrow></span>
|
|
396
439
|
</button>
|
|
397
|
-
<
|
|
440
|
+
<nav data-dropdown-menu aria-label="Výber jazyka">
|
|
398
441
|
<div>
|
|
399
442
|
<div>
|
|
400
|
-
<
|
|
401
|
-
<
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
443
|
+
<ul style="list-style: none; margin: 0; padding: 0;">
|
|
444
|
+
<li>
|
|
445
|
+
<a href="/en" hreflang="en" lang="en" data-dropdown-item aria-current="page">
|
|
446
|
+
<span data-dropdown-item-flag>🇬🇧</span>
|
|
447
|
+
English
|
|
448
|
+
</a>
|
|
449
|
+
</li>
|
|
450
|
+
<li>
|
|
451
|
+
<a href="/sk" hreflang="sk" lang="sk" data-dropdown-item>
|
|
452
|
+
<span data-dropdown-item-flag>🇸🇰</span>
|
|
453
|
+
Slovenčina
|
|
454
|
+
</a>
|
|
455
|
+
</li>
|
|
456
|
+
</ul>
|
|
408
457
|
</div>
|
|
409
458
|
</div>
|
|
410
|
-
</
|
|
459
|
+
</nav>
|
|
411
460
|
</div>
|
|
412
461
|
```
|
|
413
462
|
|
package/package.json
CHANGED
package/src/js/a11y-dropdown.js
CHANGED
|
@@ -92,17 +92,26 @@ class AccessibleDropdown {
|
|
|
92
92
|
.substr(2, 9)}`;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
const isNavigation = this.options.dropdownRole === "navigation";
|
|
96
|
+
|
|
97
|
+
// Button ARIA attributes
|
|
98
|
+
// Navigation (disclosure) pattern: aria-expanded + aria-controls only, no aria-haspopup
|
|
99
|
+
if (!isNavigation) {
|
|
100
|
+
const ariaHaspopupValue = this.options.dropdownRole === "menu" ? "true" : this.options.dropdownRole;
|
|
101
|
+
this.button.setAttribute("aria-haspopup", ariaHaspopupValue);
|
|
102
|
+
}
|
|
98
103
|
this.button.setAttribute("aria-expanded", "false");
|
|
99
104
|
this.button.setAttribute("aria-controls", this.menu.id);
|
|
100
105
|
|
|
101
|
-
// Menu ARIA attributes
|
|
102
|
-
|
|
106
|
+
// Menu ARIA attributes
|
|
107
|
+
// Navigation pattern: skip role override to preserve native element role (e.g. <nav> landmark)
|
|
108
|
+
if (!isNavigation) {
|
|
109
|
+
this.menu.setAttribute("role", this.options.dropdownRole);
|
|
110
|
+
}
|
|
103
111
|
this.menu.setAttribute("aria-labelledby", this.button.id);
|
|
104
112
|
|
|
105
|
-
// Menu items ARIA attributes
|
|
113
|
+
// Menu items ARIA attributes
|
|
114
|
+
// Navigation pattern: skip role override to preserve native element semantics (e.g. <a> links)
|
|
106
115
|
const itemRole = this.options.dropdownRole === "menu" ? "menuitem" :
|
|
107
116
|
this.options.dropdownRole === "listbox" ? "option" : null;
|
|
108
117
|
|
|
@@ -217,7 +226,7 @@ class AccessibleDropdown {
|
|
|
217
226
|
}
|
|
218
227
|
|
|
219
228
|
// Pattern-specific navigation
|
|
220
|
-
if (this.options.dropdownRole === "menu" || this.options.dropdownRole === "listbox") {
|
|
229
|
+
if (this.options.dropdownRole === "menu" || this.options.dropdownRole === "listbox" || this.options.dropdownRole === "navigation") {
|
|
221
230
|
switch (e.key) {
|
|
222
231
|
case "ArrowDown":
|
|
223
232
|
e.preventDefault();
|
|
@@ -252,8 +261,11 @@ class AccessibleDropdown {
|
|
|
252
261
|
}
|
|
253
262
|
|
|
254
263
|
handleItemClick(e, index) {
|
|
255
|
-
|
|
256
|
-
|
|
264
|
+
// Navigation pattern: let links handle click naturally (supports Ctrl+click, middle-click, etc.)
|
|
265
|
+
if (this.options.dropdownRole !== "navigation") {
|
|
266
|
+
e.preventDefault();
|
|
267
|
+
e.stopPropagation();
|
|
268
|
+
}
|
|
257
269
|
this.selectItem(index);
|
|
258
270
|
}
|
|
259
271
|
|