accessible-kit 1.0.7 → 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 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
- - Language/region selectors
228
- - User profile menus
229
- - Context menus
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 'listbox'
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 'listbox'
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
- <div data-dropdown-menu>
440
+ <nav data-dropdown-menu aria-label="Výber jazyka">
398
441
  <div>
399
442
  <div>
400
- <button data-dropdown-item>
401
- <span data-dropdown-item-flag>🇬🇧</span>
402
- English
403
- </button>
404
- <button data-dropdown-item>
405
- <span data-dropdown-item-flag>🇸🇰</span>
406
- Slovenčina
407
- </button>
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
- </div>
459
+ </nav>
411
460
  </div>
412
461
  ```
413
462
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "accessible-kit",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Lightweight, accessible UI component library with full ARIA support. Zero dependencies, vanilla JavaScript.",
5
5
  "main": "src/js/index.js",
6
6
  "type": "module",
@@ -90,9 +90,7 @@
90
90
  [data-dropdown-item] {
91
91
  display: block;
92
92
  width: 100%;
93
- text-align: left;
94
93
  cursor: pointer;
95
- text-decoration: none;
96
94
  }
97
95
 
98
96
  /* Divider */
@@ -92,17 +92,26 @@ class AccessibleDropdown {
92
92
  .substr(2, 9)}`;
93
93
  }
94
94
 
95
- // Button ARIA attributes - set aria-haspopup based on dropdown role
96
- const ariaHaspopupValue = this.options.dropdownRole === "menu" ? "true" : this.options.dropdownRole;
97
- this.button.setAttribute("aria-haspopup", ariaHaspopupValue);
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 - set role based on dropdown role
102
- this.menu.setAttribute("role", this.options.dropdownRole);
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 - set item role based on dropdown role
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
- e.preventDefault();
256
- e.stopPropagation();
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