javascriptvue.jsvuejs3vuetify.jsaccessibility

Vuetify v-text-field: Duplicate Error Announcements with Screen Readers


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>
                            

Solution

  • 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.