最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Controlling Announcement Sequence for Screen Readers in Vue Form Validation (Vuetify v-form) - Stack Overflow

programmeradmin5浏览0评论

Problem

I'm developing an accessible form in Vue with proper form validation. When a user submits an invalid form, I want to:

  1. Announce all validation errors to screen reader users
  2. Move focus to the first invalid field

However, the current implementation causes confusion for screen reader users. When focus moves to the invalid field, the screen reader announces Firstly, the field's own error message and its aria labels (automatically when focus moves) then my custom announcement with all form errors

This creates a redundant and confusing experience where the same error is announced twice but in different contexts.

Current Implementation

I'm using a combination of form validation, error collection, and programmatic focus management. When validation fails:

  1. I collect all error messages
  2. Announce them via an aria-live region
  3. Move focus to the first invalid field

But with this implementation it is not working in order. I want to have the errors announced first and then the trigger the current field announcements like mentioned with aria attributes.

The problematic code:

const submit = async (event) => {
  event.preventDefault();
  const { valid: isValid } = await myForm.value.validate();

  if (!isValid) {
    // Get all form items with errors
    const formItems = myForm.value.items;
    
    // Collect all error messages
    const errors = formItems.reduce((acc, item) => {
      if (item.errorMessages?.length > 0) {
        acc.push(...item.errorMessages);
      }
      return acc;
    }, []);
    
    // Announce all errors
    if (errors.length > 0) {
      announceToScreenReader(`Please correct the following errors: ${errors.join('. ')}`);
    }
    
    // Find and focus the first invalid field
    const firstInvalidField = formItems.find(item => 
      item.errorMessages?.length > 0
    );
    
    if (firstInvalidField) {
      const inputElement = document.getElementById(firstInvalidField.id);
      if (inputElement) {
        inputElement.focus();
        inputElement.scrollIntoView({ behavior: 'smooth' });
      }
    }
  }
};

const announceToScreenReader = (message) => {
  const announcer = document.getElementById('sr-announcer');
  if (!announcer) {
    const newAnnouncer = document.createElement('div');
    newAnnouncer.id = 'sr-announcer';
    newAnnouncer.setAttribute('aria-live', 'polite');
    newAnnouncer.setAttribute('aria-atomic', 'true');
    newAnnouncer.classList.add('sr-only');
    document.body.appendChild(newAnnouncer);
    setTimeout(() => {
      newAnnouncer.textContent = message;
    }, 100);
  } else {
    announcer.textContent = '';
    setTimeout(() => {
      announcer.textContent = message;
    }, 100);
  }
};

And the template

<v-form ref="myForm" @submit.prevent="submit">
  <v-text-field 
    v-model="firstName"
    :rules="[nameValidation.required, nameValidation.min]"
    :aria-label="'First Name'"
  >
    <template #message="arg">
      <div aria-live="off" id="firstNameError">{{ arg.message }}</div>
    </template>
  </v-text-field>
  
  <!-- Other form fields... -->
  
  <v-btn type="submit">Submit</v-btn>
</v-form>

How can I properly sequence these announcements so that:

The screen reader first announces the complete list of errors (via the aria-live region), Then trigger announcements for first invalid field which is focused

Problem

I'm developing an accessible form in Vue with proper form validation. When a user submits an invalid form, I want to:

  1. Announce all validation errors to screen reader users
  2. Move focus to the first invalid field

However, the current implementation causes confusion for screen reader users. When focus moves to the invalid field, the screen reader announces Firstly, the field's own error message and its aria labels (automatically when focus moves) then my custom announcement with all form errors

This creates a redundant and confusing experience where the same error is announced twice but in different contexts.

Current Implementation

I'm using a combination of form validation, error collection, and programmatic focus management. When validation fails:

  1. I collect all error messages
  2. Announce them via an aria-live region
  3. Move focus to the first invalid field

But with this implementation it is not working in order. I want to have the errors announced first and then the trigger the current field announcements like mentioned with aria attributes.

The problematic code:

const submit = async (event) => {
  event.preventDefault();
  const { valid: isValid } = await myForm.value.validate();

  if (!isValid) {
    // Get all form items with errors
    const formItems = myForm.value.items;
    
    // Collect all error messages
    const errors = formItems.reduce((acc, item) => {
      if (item.errorMessages?.length > 0) {
        acc.push(...item.errorMessages);
      }
      return acc;
    }, []);
    
    // Announce all errors
    if (errors.length > 0) {
      announceToScreenReader(`Please correct the following errors: ${errors.join('. ')}`);
    }
    
    // Find and focus the first invalid field
    const firstInvalidField = formItems.find(item => 
      item.errorMessages?.length > 0
    );
    
    if (firstInvalidField) {
      const inputElement = document.getElementById(firstInvalidField.id);
      if (inputElement) {
        inputElement.focus();
        inputElement.scrollIntoView({ behavior: 'smooth' });
      }
    }
  }
};

const announceToScreenReader = (message) => {
  const announcer = document.getElementById('sr-announcer');
  if (!announcer) {
    const newAnnouncer = document.createElement('div');
    newAnnouncer.id = 'sr-announcer';
    newAnnouncer.setAttribute('aria-live', 'polite');
    newAnnouncer.setAttribute('aria-atomic', 'true');
    newAnnouncer.classList.add('sr-only');
    document.body.appendChild(newAnnouncer);
    setTimeout(() => {
      newAnnouncer.textContent = message;
    }, 100);
  } else {
    announcer.textContent = '';
    setTimeout(() => {
      announcer.textContent = message;
    }, 100);
  }
};

And the template

<v-form ref="myForm" @submit.prevent="submit">
  <v-text-field 
    v-model="firstName"
    :rules="[nameValidation.required, nameValidation.min]"
    :aria-label="'First Name'"
  >
    <template #message="arg">
      <div aria-live="off" id="firstNameError">{{ arg.message }}</div>
    </template>
  </v-text-field>
  
  <!-- Other form fields... -->
  
  <v-btn type="submit">Submit</v-btn>
</v-form>

How can I properly sequence these announcements so that:

The screen reader first announces the complete list of errors (via the aria-live region), Then trigger announcements for first invalid field which is focused

Share Improve this question asked Mar 8 at 10:13 user20042604user20042604 731 silver badge8 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

I don't think there is a way you can do this reliably. As soon as you manually put focus on the invalid input, the screen reader is going to stop announcing your list of errors in the live region and announce the input you focused. You could put a delay on the focus to give the screen reader a chance to announce all of the errors in the live region, but then you'll be guessing how long that will take.

One common method for announcing a list of errors is to move focus to the list and make each error message a link to its input. You could also not move the focus at all and just add the errors to the live region. Also, creating a master list of errors is not required. You can just add the error messages to their inputs and put focus on the first invalid input. If you are going to do a master list then I would recommend you move focus to it and add links to the inputs.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论