accessible-kit 1.0.5 → 1.0.7

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,43 @@ 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.7] - 2026-01-26
9
+
10
+ ### Changed
11
+ - **All Components - Focus Indicator Improvements**: Migrated from `:focus` to `:focus-visible` for better keyboard navigation UX
12
+ - **Breaking Change for Custom Themes**: If you have custom theme files, update `:focus` selectors to `:focus-visible`
13
+ - **Complete separation of concerns**: ALL visual styling (including `outline` properties) moved from `*.core.css` to `*.theme.css` files
14
+ - Core CSS files now contain ZERO visual styling - only structural layout and behavior
15
+ - Theme CSS files contain all focus styling including `outline`, `outline-offset`, and `outline-color`
16
+ - **Benefits**:
17
+ - Focus indicators only appear for keyboard navigation, not on mouse clicks
18
+ - Perfect separation between core functionality and visual design
19
+ - Complete customization freedom - modify or remove all focus styling in theme files
20
+ - No hardcoded visual defaults in core files
21
+ - **Affected components**: Dropdown, Modal, Offcanvas, Tabs, Accordion
22
+ - **Migration guide**:
23
+ - Update your custom theme `:focus` selectors to `:focus-visible`
24
+ - All `outline` customization should now be done in `*.theme.css` files only
25
+ - Updated documentation in README with focus-visible information
26
+
27
+ ## [1.0.6] - 2026-01-15
28
+
29
+ ### Added
30
+ - **Dropdown - Multiple ARIA Pattern Support**: Added `data-dropdown-role` attribute to support dialog and listbox patterns in addition to the default menu pattern
31
+ - **New attribute**: `data-dropdown-role` accepts `"menu"` (default), `"dialog"`, or `"listbox"`
32
+ - Automatically sets appropriate `aria-haspopup` value based on role type:
33
+ - `role="menu"` → `aria-haspopup="true"`
34
+ - `role="dialog"` → `aria-haspopup="dialog"`
35
+ - `role="listbox"` → `aria-haspopup="listbox"`
36
+ - Conditionally applies correct item roles (`menuitem`, none for dialog, `option` for listbox)
37
+ - Pattern-specific keyboard navigation:
38
+ - Menu/listbox: Arrow keys, Home/End, Tab closes dropdown
39
+ - Dialog: Arrow keys, Tab navigates within (doesn't close)
40
+ - **Use case example**: Radio playlists, media controls, or complex interactive widgets that aren't semantic "menus"
41
+ - Added comprehensive documentation and live example (radio playlist) to demo page
42
+ - Updated README with ARIA pattern guidance and when to use each pattern
43
+ - **Backwards compatible**: Defaults to `"menu"` pattern, existing implementations unchanged
44
+
8
45
  ## [1.0.5] - 2025-12-21
9
46
 
10
47
  ### Fixed
@@ -134,6 +171,8 @@ document.addEventListener('DOMContentLoaded', () => {
134
171
  - Zero dependencies
135
172
  - Full TypeScript-ready exports
136
173
 
174
+ [1.0.7]: https://github.com/5ulo/accessible-kit/compare/v1.0.6...v1.0.7
175
+ [1.0.6]: https://github.com/5ulo/accessible-kit/compare/v1.0.5...v1.0.6
137
176
  [1.0.5]: https://github.com/5ulo/accessible-kit/compare/v1.0.4...v1.0.5
138
177
  [1.0.4]: https://github.com/5ulo/accessible-kit/compare/v1.0.3...v1.0.4
139
178
  [1.0.3]: https://github.com/5ulo/accessible-kit/compare/v1.0.2...v1.0.3
package/README.md CHANGED
@@ -19,6 +19,7 @@ A collection of fully accessible UI components for modern web applications. Buil
19
19
  - **♿ Inclusive** - High contrast mode and reduced motion support
20
20
  - **🔧 Framework Agnostic** - Works with any framework or vanilla JS
21
21
  - **📦 Tree-shakeable** - Import only what you need
22
+ - **⌨️ Focus Visible** - Uses `:focus-visible` for better keyboard navigation UX
22
23
 
23
24
  ## 📚 Table of Contents
24
25
 
@@ -168,11 +169,12 @@ const dropdown = new AccessibleDropdown(element, {
168
169
 
169
170
  ## Dropdown
170
171
 
171
- Fully accessible dropdown component with keyboard navigation and ARIA support.
172
+ Fully accessible dropdown component with keyboard navigation and ARIA support. Supports multiple ARIA patterns (menu, dialog, listbox) for different use cases.
172
173
 
173
174
  ### Features
174
175
 
175
- - ✅ Full ARIA attributes support
176
+ - ✅ Full ARIA attributes support (menu, dialog, and listbox patterns)
177
+ - ✅ Flexible role patterns via `data-dropdown-role` attribute
176
178
  - ✅ Keyboard navigation (arrows, Enter, Space, Esc, Home, End)
177
179
  - ✅ Focus management
178
180
  - ✅ Auto-close on outside click
@@ -213,6 +215,57 @@ Fully accessible dropdown component with keyboard navigation and ARIA support.
213
215
  </div>
214
216
  ```
215
217
 
218
+ ### ARIA Patterns
219
+
220
+ The dropdown component supports multiple ARIA patterns to match different use cases. By default, it uses the **menu pattern**, but you can change this using the `data-dropdown-role` attribute.
221
+
222
+ #### When to Use Each Pattern
223
+
224
+ **Menu Pattern (default)** - Use `role="menu"` for:
225
+ - Navigation menus
226
+ - Action menus (Edit, Delete, etc.)
227
+ - Language/region selectors
228
+ - User profile menus
229
+ - Context menus
230
+
231
+ **Dialog Pattern** - Use `data-dropdown-role="dialog"` for:
232
+ - Media player playlists/controls
233
+ - Complex interactive widgets
234
+ - Custom form controls
235
+ - Rich content with multiple focusable elements
236
+ - Non-menu disclosure patterns
237
+
238
+ **Example: Dialog Pattern (Radio Playlist)**
239
+
240
+ ```html
241
+ <!-- Use data-dropdown-role="dialog" for playlists, controls, or interactive widgets -->
242
+ <div data-dropdown data-dropdown-role="dialog">
243
+ <button data-dropdown-button>
244
+ 🎵 Playlist
245
+ <span data-dropdown-arrow></span>
246
+ </button>
247
+ <div data-dropdown-menu>
248
+ <div>
249
+ <div>
250
+ <button data-dropdown-item>Station 1</button>
251
+ <button data-dropdown-item>Station 2</button>
252
+ <button data-dropdown-item>Station 3</button>
253
+ </div>
254
+ </div>
255
+ </div>
256
+ </div>
257
+
258
+ <!-- This automatically sets:
259
+ - Button: aria-haspopup="dialog" (instead of "true")
260
+ - Menu: role="dialog" (instead of "menu")
261
+ - Items: No role attribute (instead of "menuitem")
262
+ - Keyboard: Tab allowed within dropdown (menu pattern closes on Tab)
263
+ -->
264
+ ```
265
+
266
+ **Why use dialog pattern?**
267
+ 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
+
216
269
  ### JavaScript Initialization
217
270
 
218
271
  ```javascript
@@ -225,6 +278,7 @@ document.addEventListener('DOMContentLoaded', () => {
225
278
 
226
279
  // Or manual initialization with options
227
280
  const dropdown = new Dropdown(element, {
281
+ dropdownRole: 'menu', // 'menu' (default), 'dialog', or 'listbox'
228
282
  closeOnSelect: true,
229
283
  closeOnOutsideClick: true,
230
284
  closeOnEscape: true,
@@ -269,6 +323,9 @@ Dropdowns have **CSS Grid animations enabled by default**. The animation automat
269
323
  **Data attributes:**
270
324
 
271
325
  ```html
326
+ <!-- Use dialog pattern for non-menu content -->
327
+ <div data-dropdown data-dropdown-role="dialog">
328
+
272
329
  <!-- Keep dropdown open after selection -->
273
330
  <div data-dropdown data-close-on-select="false">
274
331
 
@@ -283,6 +340,7 @@ Dropdowns have **CSS Grid animations enabled by default**. The animation automat
283
340
 
284
341
  ```javascript
285
342
  {
343
+ dropdownRole: 'menu', // ARIA pattern: 'menu', 'dialog', or 'listbox'
286
344
  closeOnSelect: true, // Close after selecting item
287
345
  closeOnOutsideClick: true, // Close on outside click
288
346
  closeOnEscape: true, // Close on Escape key
@@ -295,12 +353,21 @@ Dropdowns have **CSS Grid animations enabled by default**. The animation automat
295
353
 
296
354
  ### Keyboard Navigation
297
355
 
356
+ **Menu Pattern (default):**
357
+
298
358
  - **Enter** / **Space** - Open/close menu or select item
299
359
  - **↓** / **↑** - Navigate between items
300
360
  - **Home** / **End** - Jump to first/last item
301
361
  - **Esc** - Close menu and return focus to button
302
362
  - **Tab** - Close menu and move focus
303
363
 
364
+ **Dialog Pattern:**
365
+
366
+ - **Enter** / **Space** - Open/close or select item
367
+ - **↓** / **↑** - Navigate between items
368
+ - **Esc** - Close and return focus to button
369
+ - **Tab** / **Shift+Tab** - Cycle through items within dialog (does not close)
370
+
304
371
  ### Variants
305
372
 
306
373
  **Navigation menu:**
@@ -989,6 +1056,7 @@ Contains only logic, positioning, layout, and behavior:
989
1056
  - Visibility states
990
1057
  - Animations
991
1058
  - Responsive breakpoints
1059
+ - **NO visual styling** - completely theme-agnostic
992
1060
 
993
1061
  **Do not modify** unless changing component functionality.
994
1062
 
@@ -1000,6 +1068,7 @@ Contains all visual styling:
1000
1068
  - Typography
1001
1069
  - Borders and border-radius
1002
1070
  - Shadows
1071
+ - **All focus indicators** (`:focus-visible` with `outline`, `outline-offset`, etc.)
1003
1072
  - Dark mode
1004
1073
  - High contrast mode
1005
1074
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "accessible-kit",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
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",
@@ -31,7 +31,7 @@
31
31
  background: #f9fafb;
32
32
  }
33
33
 
34
- [data-accordion-trigger]:focus {
34
+ [data-accordion-trigger]:focus-visible {
35
35
  outline: 2px solid #3b82f6;
36
36
  outline-offset: -2px;
37
37
  }
@@ -78,7 +78,7 @@
78
78
  background: #2563eb;
79
79
  }
80
80
 
81
- [data-accordion-toggle]:focus {
81
+ [data-accordion-toggle]:focus-visible {
82
82
  outline: 2px solid #3b82f6;
83
83
  outline-offset: 2px;
84
84
  }
@@ -112,7 +112,7 @@
112
112
  border-color: #d1d5db;
113
113
  }
114
114
 
115
- [data-accordion-toggle-all]:focus {
115
+ [data-accordion-toggle-all]:focus-visible {
116
116
  outline: 2px solid #3b82f6;
117
117
  outline-offset: 2px;
118
118
  }
@@ -214,7 +214,7 @@
214
214
  border-width: 2px;
215
215
  }
216
216
 
217
- [data-accordion-trigger]:focus {
217
+ [data-accordion-trigger]:focus-visible {
218
218
  outline-width: 3px;
219
219
  }
220
220
  }
@@ -16,10 +16,6 @@
16
16
  cursor: pointer;
17
17
  }
18
18
 
19
- [data-dropdown-button]:focus {
20
- outline-offset: 2px;
21
- }
22
-
23
19
  /* Button icon/arrow */
24
20
  [data-dropdown-arrow] {
25
21
  display: inline-block;
@@ -99,10 +95,6 @@
99
95
  text-decoration: none;
100
96
  }
101
97
 
102
- [data-dropdown-item]:focus {
103
- outline: none;
104
- }
105
-
106
98
  /* Divider */
107
99
  [data-dropdown-divider] {
108
100
  height: 1px;
@@ -130,9 +122,6 @@
130
122
  }
131
123
 
132
124
  /* Navigation variant */
133
- [data-dropdown][data-variant="nav"] [data-dropdown-button]:focus {
134
- outline-offset: 2px;
135
- }
136
125
 
137
126
  /* Language switcher variant */
138
127
  [data-dropdown][data-variant="language"] [data-dropdown-item] {
@@ -153,13 +142,6 @@
153
142
  }
154
143
  }
155
144
 
156
- /* High contrast mode support */
157
- @media (prefers-contrast: high) {
158
- [data-dropdown-item]:focus {
159
- outline-offset: -2px;
160
- }
161
- }
162
-
163
145
  /* Reduced motion support - disable animations */
164
146
  @media (prefers-reduced-motion: reduce) {
165
147
  [data-dropdown-menu]:not([data-no-animation]),
@@ -20,7 +20,7 @@
20
20
  border-color: #9ca3af;
21
21
  }
22
22
 
23
- [data-dropdown-button]:focus {
23
+ [data-dropdown-button]:focus-visible {
24
24
  outline: 2px solid #3b82f6;
25
25
  border-color: #3b82f6;
26
26
  }
@@ -61,9 +61,10 @@
61
61
  }
62
62
 
63
63
  [data-dropdown-item]:hover,
64
- [data-dropdown-item]:focus {
64
+ [data-dropdown-item]:focus-visible {
65
65
  background: #f3f4f6;
66
66
  color: #111827;
67
+ outline: none;
67
68
  }
68
69
 
69
70
  [data-dropdown-item]:active {
@@ -98,7 +99,7 @@
98
99
  background: rgba(0, 0, 0, 0.05);
99
100
  }
100
101
 
101
- [data-dropdown][data-variant="nav"] [data-dropdown-button]:focus {
102
+ [data-dropdown][data-variant="nav"] [data-dropdown-button]:focus-visible {
102
103
  outline: 2px solid currentColor;
103
104
  }
104
105
 
@@ -122,8 +123,9 @@
122
123
  border-width: 2px;
123
124
  }
124
125
 
125
- [data-dropdown-item]:focus {
126
+ [data-dropdown-item]:focus-visible {
126
127
  outline: 2px solid currentColor;
128
+ outline-offset: -2px;
127
129
  }
128
130
  }
129
131
 
@@ -160,9 +162,10 @@
160
162
  }
161
163
 
162
164
  [data-dropdown-item]:hover,
163
- [data-dropdown-item]:focus {
165
+ [data-dropdown-item]:focus-visible {
164
166
  background: #374151;
165
167
  color: #fff;
168
+ outline: none;
166
169
  }
167
170
 
168
171
  [data-dropdown-item]:active {
@@ -110,11 +110,6 @@ body.modal-open {
110
110
  overflow: hidden;
111
111
  }
112
112
 
113
- /* Focus trap */
114
- [data-modal-dialog]:focus {
115
- outline: none;
116
- }
117
-
118
113
  /* Responsive */
119
114
  @media (max-width: 640px) {
120
115
  [data-modal-dialog] {
@@ -16,6 +16,10 @@
16
16
  0 10px 10px -5px rgba(0, 0, 0, 0.04);
17
17
  }
18
18
 
19
+ [data-modal-dialog]:focus-visible {
20
+ outline: none;
21
+ }
22
+
19
23
  /* Modal header */
20
24
  [data-modal-header] {
21
25
  padding: 1.5rem;
@@ -66,7 +70,7 @@
66
70
  color: #111827;
67
71
  }
68
72
 
69
- [data-modal-close]:focus {
73
+ [data-modal-close]:focus-visible {
70
74
  outline: 2px solid #3b82f6;
71
75
  outline-offset: 2px;
72
76
  }
@@ -200,7 +204,7 @@
200
204
  border: 2px solid currentColor;
201
205
  }
202
206
 
203
- [data-modal-close]:focus {
207
+ [data-modal-close]:focus-visible {
204
208
  outline-width: 3px;
205
209
  }
206
210
  }
@@ -112,11 +112,6 @@ body.offcanvas-open {
112
112
  overflow: hidden;
113
113
  }
114
114
 
115
- /* Focus trap */
116
- [data-offcanvas-panel]:focus {
117
- outline: none;
118
- }
119
-
120
115
  /* Reduced motion support */
121
116
  @media (prefers-reduced-motion: reduce) {
122
117
  [data-offcanvas-panel],
@@ -2,6 +2,11 @@
2
2
  /* Obsahuje vizualne nastavenia: farby, velkosti, bordery, radiusy, spacing */
3
3
  /* Pre zmenu vzhladu upravte tento subor */
4
4
 
5
+ /* Focus visible for the panel itself (focus trap) */
6
+ [data-offcanvas-panel]:focus-visible {
7
+ outline: none;
8
+ }
9
+
5
10
  /* Focus visible for all focusable elements inside offcanvas */
6
11
  [data-offcanvas-panel] :focus-visible {
7
12
  outline: 2px solid #3b82f6;
@@ -46,10 +46,6 @@
46
46
  pointer-events: none;
47
47
  }
48
48
 
49
- [data-tabs-tab]:focus {
50
- outline-offset: -2px;
51
- }
52
-
53
49
  /* Tab panels container */
54
50
  [data-tabs-panels] {
55
51
  position: relative;
@@ -64,11 +60,6 @@
64
60
  display: block;
65
61
  }
66
62
 
67
- /* Focus within panel */
68
- [data-tabs-panel]:focus {
69
- outline: none;
70
- }
71
-
72
63
  /* View Transitions API uses default cross-fade animation */
73
64
  /* Customization via pseudo-elements */
74
65
  ::view-transition-old(root),
@@ -35,8 +35,9 @@
35
35
  background: #f9fafb;
36
36
  }
37
37
 
38
- [data-tabs-tab]:focus {
38
+ [data-tabs-tab]:focus-visible {
39
39
  outline: 2px solid #3b82f6;
40
+ outline-offset: -2px;
40
41
  color: #374151;
41
42
  }
42
43
 
@@ -80,6 +81,10 @@
80
81
  line-height: 1.6;
81
82
  }
82
83
 
84
+ [data-tabs-panel]:focus-visible {
85
+ outline: none;
86
+ }
87
+
83
88
  /* Boxed variant */
84
89
  [data-tabs][data-tabs][data-variant="boxed"] [data-tabs-list] {
85
90
  border-bottom: 1px solid #e5e7eb;
@@ -136,7 +141,7 @@
136
141
  color: #fff;
137
142
  }
138
143
 
139
- [data-tabs][data-tabs][data-variant="pills"] [data-tabs-tab]:focus {
144
+ [data-tabs][data-tabs][data-variant="pills"] [data-tabs-tab]:focus-visible {
140
145
  outline-color: #3b82f6;
141
146
  }
142
147
 
@@ -222,7 +227,7 @@
222
227
  border-width: 2px;
223
228
  }
224
229
 
225
- [data-tabs-tab]:focus {
230
+ [data-tabs-tab]:focus-visible {
226
231
  outline-width: 3px;
227
232
  }
228
233
  }
@@ -246,7 +251,7 @@
246
251
  background: #1f2937;
247
252
  }
248
253
 
249
- [data-tabs-tab]:focus {
254
+ [data-tabs-tab]:focus-visible {
250
255
  color: #f9fafb;
251
256
  }
252
257
 
@@ -18,6 +18,7 @@ class AccessibleDropdown {
18
18
 
19
19
  // Options
20
20
  this.options = {
21
+ dropdownRole: options.dropdownRole || "menu",
21
22
  closeOnSelect: options.closeOnSelect !== false,
22
23
  closeOnOutsideClick: options.closeOnOutsideClick !== false,
23
24
  closeOnEscape: options.closeOnEscape !== false,
@@ -91,18 +92,24 @@ class AccessibleDropdown {
91
92
  .substr(2, 9)}`;
92
93
  }
93
94
 
94
- // Button ARIA attributes
95
- this.button.setAttribute("aria-haspopup", "true");
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);
96
98
  this.button.setAttribute("aria-expanded", "false");
97
99
  this.button.setAttribute("aria-controls", this.menu.id);
98
100
 
99
- // Menu ARIA attributes
100
- this.menu.setAttribute("role", "menu");
101
+ // Menu ARIA attributes - set role based on dropdown role
102
+ this.menu.setAttribute("role", this.options.dropdownRole);
101
103
  this.menu.setAttribute("aria-labelledby", this.button.id);
102
104
 
103
- // Menu items ARIA attributes
105
+ // Menu items ARIA attributes - set item role based on dropdown role
106
+ const itemRole = this.options.dropdownRole === "menu" ? "menuitem" :
107
+ this.options.dropdownRole === "listbox" ? "option" : null;
108
+
104
109
  this.items.forEach((item) => {
105
- item.setAttribute("role", "menuitem");
110
+ if (itemRole) {
111
+ item.setAttribute("role", itemRole);
112
+ }
106
113
  if (!item.hasAttribute("tabindex")) {
107
114
  item.setAttribute("tabindex", "-1");
108
115
  }
@@ -184,32 +191,64 @@ class AccessibleDropdown {
184
191
  }
185
192
 
186
193
  handleItemKeydown(e, index) {
194
+ // Common navigation for all patterns
187
195
  switch (e.key) {
188
196
  case "Enter":
189
197
  case " ":
190
198
  e.preventDefault();
191
199
  this.selectItem(index);
192
200
  break;
193
- case "ArrowDown":
194
- e.preventDefault();
195
- this.focusNextItem();
196
- break;
197
- case "ArrowUp":
198
- e.preventDefault();
199
- this.focusPreviousItem();
200
- break;
201
- case "Home":
202
- e.preventDefault();
203
- this.setFocusedItem(0);
204
- break;
205
- case "End":
206
- e.preventDefault();
207
- this.setFocusedItem(this.items.length - 1);
208
- break;
209
201
  case "Tab":
210
- this.close();
202
+ // For dialog pattern, Tab should cycle within items
203
+ // For menu and listbox, close on Tab
204
+ if (this.options.dropdownRole === "dialog") {
205
+ e.preventDefault();
206
+ // Tab forward or backward through items
207
+ if (e.shiftKey) {
208
+ this.focusPreviousItem();
209
+ } else {
210
+ this.focusNextItem();
211
+ }
212
+ } else {
213
+ // Menu and listbox patterns close on Tab
214
+ this.close();
215
+ }
211
216
  break;
212
217
  }
218
+
219
+ // Pattern-specific navigation
220
+ if (this.options.dropdownRole === "menu" || this.options.dropdownRole === "listbox") {
221
+ switch (e.key) {
222
+ case "ArrowDown":
223
+ e.preventDefault();
224
+ this.focusNextItem();
225
+ break;
226
+ case "ArrowUp":
227
+ e.preventDefault();
228
+ this.focusPreviousItem();
229
+ break;
230
+ case "Home":
231
+ e.preventDefault();
232
+ this.setFocusedItem(0);
233
+ break;
234
+ case "End":
235
+ e.preventDefault();
236
+ this.setFocusedItem(this.items.length - 1);
237
+ break;
238
+ }
239
+ } else if (this.options.dropdownRole === "dialog") {
240
+ // Dialog pattern: Arrow keys for navigation (less strict)
241
+ switch (e.key) {
242
+ case "ArrowDown":
243
+ e.preventDefault();
244
+ this.focusNextItem();
245
+ break;
246
+ case "ArrowUp":
247
+ e.preventDefault();
248
+ this.focusPreviousItem();
249
+ break;
250
+ }
251
+ }
213
252
  }
214
253
 
215
254
  handleItemClick(e, index) {
@@ -320,6 +359,7 @@ function initDropdowns() {
320
359
 
321
360
  dropdowns.forEach((dropdown) => {
322
361
  const options = {
362
+ dropdownRole: dropdown.dataset.dropdownRole || "menu",
323
363
  closeOnSelect: dropdown.dataset.closeOnSelect !== "false",
324
364
  closeOnOutsideClick:
325
365
  dropdown.dataset.closeOnOutsideClick !== "false",