Skip to content

Commit 990eccc

Browse files
committed
feat(select): add uid prop to improve a11y with aria-labelledby, aria-owns and aria-controls
1 parent 4b20e34 commit 990eccc

File tree

4 files changed

+26
-0
lines changed

4 files changed

+26
-0
lines changed

docs/props.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,14 @@ The `id` attribute to be passed to the `<input />` element. This is useful for a
187187

188188
A custom class to be passed to the select control.
189189

190+
## uid
191+
192+
**Type**: `string | number`
193+
194+
**Default**: `number`
195+
196+
A unique identifier to be passed to the select control. Will be used on multiple `id` attributes for accessibility purposes such as `aria-owns`, `aria-controls`, etc.
197+
190198
## aria
191199

192200
**Type**: `{ labelledby?: string; required?: boolean; }`

src/Select.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import MenuOption from "./MenuOption.vue";
99
import MultiValue from "./MultiValue.vue";
1010
import Placeholder from "./Placeholder.vue";
1111
import Spinner from "./Spinner.vue";
12+
import { uniqueId } from "./utils";
1213
1314
const props = withDefaults(
1415
defineProps<Props<GenericOption, OptionValue>>(),
@@ -25,6 +26,7 @@ const props = withDefaults(
2526
closeOnSelect: true,
2627
teleport: undefined,
2728
inputId: undefined,
29+
uid: uniqueId(),
2830
aria: undefined,
2931
filterBy: (option: GenericOption, label: string, search: string) => label.toLowerCase().includes(search.toLowerCase()),
3032
getOptionValue: (option: GenericOption) => option.value,
@@ -348,6 +350,7 @@ onBeforeUnmount(() => {
348350
@click="handleControlClick($event)"
349351
>
350352
<div
353+
:id="`vue-select-${uid}-combobox`"
351354
class="value-container"
352355
:class="{ 'multi': isMulti, 'has-value': selectedOptions.length > 0 }"
353356
role="combobox"
@@ -357,6 +360,8 @@ onBeforeUnmount(() => {
357360
:aria-labelledby="aria?.labelledby"
358361
:aria-label="selectedOptions.length ? selectedOptions.map(getOptionLabel).join(', ') : ''"
359362
:aria-required="aria?.required"
363+
:aria-owns="`vue-select-${uid}-listbox`"
364+
:aria-controls="`vue-select-${uid}-listbox`"
360365
aria-haspopup="true"
361366
>
362367
<Placeholder
@@ -405,6 +410,7 @@ onBeforeUnmount(() => {
405410
tabindex="0"
406411
type="text"
407412
aria-autocomplete="list"
413+
:aria-labelledby="`vue-select-${uid}-combobox`"
408414
:disabled="isDisabled"
409415
placeholder=""
410416
@mousedown="openMenu()"
@@ -450,6 +456,7 @@ onBeforeUnmount(() => {
450456
>
451457
<div
452458
v-if="menuOpen"
459+
:id="`vue-select-${uid}-listbox`"
453460
ref="menu"
454461
class="menu"
455462
role="listbox"

src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ export type Props<GenericOption, OptionValue> = {
8888
*/
8989
class?: string;
9090

91+
/**
92+
* Unique identifier to identify the select component, using `id` attribute.
93+
* This is useful for accessibility.
94+
*/
95+
uid?: string | number;
96+
9197
/**
9298
* ARIA attributes to describe the select component. This is useful for accessibility.
9399
*/

src/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
let idCount = 0;
2+
3+
export function uniqueId() {
4+
return ++idCount;
5+
}

0 commit comments

Comments
 (0)