I'm experiencing an issue with screen reader error announcements in a Vuetify form. When using a v-text-field with validation rules, screen readers (specifically NVDA) announce the same error message twice when a user moves between form fields.
The error it is announcing twice in my case is 'Aeroplane number is required' when I am using tab key to navigate. Similarly with the last name the error is pronounced twice.
<v-col cols="12" md="4" class="py-0">
<v-text-field
class="aeroplanNumber_unmasking"
:label="t('aeroplanNumber') + '*'"
:aria-label="t('aeroplanNumber')"
v-model="aeroplanNumber"
variant="underlined"
@keypress="onlyNumeric"
:rules="[aeroplanNumberVaidation.required, aeroplanNumberVaidation.min, aeroplanNumberVaidation.max]"
maxlength="9"
>
</v-text-field>
</v-col>
<v-col cols="12" md="4" class="py-0">
<v-text-field
class="lastName_unmasking"
:label="t('surName') + '*'"
:aria-label="t('surName')"
v-model="surName"
validate-on="blur"
variant="underlined"
:rules="[lastNameValidation.required, lastNameValidation.min]"
maxlength="15"
>
</v-text-field>
<script setup>
const aeroplanNumberVaidation = {
required: value => !!value || t('loyaltyAeroplanRequired'),
min: v => v.length >= 9 || t('aeroplanNumberLessError'),
max: v => v.length <= 9 || t('aeroplanNumberGreaterError'),
};
const lastNameValidation = {
required: value => !!value || t('surNameRequired'),
min: v => v.length >= 3 || t('surNameError'),
};
</script>
This problem seems to be caused by Vuetify
's checksum mechanism and the default attribute of aria-live
.
Since Vuetify
does a checksum when loading a page by default, and the default aria-live
value is polite
, it causes screen readers (such as NVDA) to read the error message early when the page is loaded, here is the official documentation on aria-live:polite
:
Normally, only aria-live="polite" is used. Any region which receives updates that are important for the user to receive, but not so rapid as to be annoying, should receive this attribute. The screen reader will speak changes whenever the user is idle.
By Aria Docs
He will only read it aloud once after I try to change aria-live="polite"
to aria-live="off"
for the error message.
Unintuitively, aria-live="off" does not indicate that changes should not be announced. When an element has aria-live="off" (or has a role with this implicit value, such as role="marquee" or role="timer"), changes to the element's content are only supposed to be announced when focus is on, or inside, the element.
By Aria Docs
<v-col cols="12" md="4" class="py-0">
<v-text-field
class="aeroplanNumber_unmasking"
:label="t('aeroplanNumber') + '*'"
:aria-label="t('aeroplanNumber')"
v-model:model-value="aeroplanNumber"
variant="underlined"
@keypress="onlyNumeric"
:rules="[
aeroplanNumberVaidation.required,
aeroplanNumberVaidation.min,
aeroplanNumberVaidation.max,
]"
>
<template #message="arg">
<div aria-live="off" id="aeroplanNumberError">
{{ arg.message }}
</div>
</template>
</v-text-field>
</v-col>
<v-col cols="12" md="4" class="py-0">
<v-text-field
class="lastName_unmasking"
:label="t('surName') + '*'"
:aria-label="t('surName')"
v-model="surName"
validate-on="blur"
variant="underlined"
:rules="[lastNameValidation.required, lastNameValidation.min]"
maxlength="15"
>
<template #message="arg">
<div aria-live="off" id="aeroplanNumberError">
{{ arg.message }}
</div>
</template>
</v-text-field>
</v-col>
I hope this helps.