Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Announcing information to a screen reader in vue.js using aria-live

I am trying to get a vue component to announce information dynamically to a screen reader when different events occur on my site.

I have it working to where clicking a button will populate a span that is aria-live="assertive" and role="alert" with text. This works decently the first time, however, clicking other buttons with similar behavior causes NVDA to read the previous text twice before reading the new text. This seems to be happening in vue, but not with a similar setup using jquery, so I'm guessing it has something to do with the way vue renders to the DOM.

I'm hoping there is some way to workaround this problem or perhaps a better way to read the text to the user that would not have this issue. Any help is greatly appreciated.

Here is a simple component I set up in a working code sandbox to show the problem I am having (navigate to components/HelloWorld.vue for the code) -- Note: This sandbox has changed per the answer below. Full code for the component is below:

export default {
  name: "HelloWorld",
  data() {
    return {
      ariaText: ""
    };
  },
  methods: {
    button1() {
      this.ariaText = "This is a bunch of cool text to read to screen readers.";
    },
    button2() {
      this.ariaText = "This is more cool text to read to screen readers.";
    },
    button3() {
      this.ariaText = "This text is not cool.";
    }
  }
};
<template>
  <div>
    <button @click="button1">1</button>
    <button @click="button2">2</button>
    <button @click="button3">3</button><br/>
    <span role="alert" aria-live="assertive">{{ariaText}}</span>
  </div>
</template>
like image 321
Will P. Avatar asked Jun 11 '18 22:06

Will P.


People also ask

Can I use aria live?

aria-live="assertive" should only be used for time-sensitive/critical notifications that absolutely require the user's immediate attention. Generally, a change to an assertive live region will interrupt any announcement a screen reader is currently making.

What is the difference between aria live assertive and aria Live polite?

As per my understanding aria-live="assertive" will get the higher priority and wipe off the queue, whereas aria-live="polite" is having low priority over aria-live="assertive" and will continue with queue.

How do I trigger an event at the Vue?

We can trigger events on an element with Vue. js by assigning a ref to the element we want to trigger events for. Then we can call methods on the element assigned to the ref to trigger the event.


1 Answers

Ok so what I've found works way more consistently is instead of replacing the text in the element with new text, to add a new element to a parent container with the new text to be read. Instead of storing the text as a single string, I am storing it in an array of strings which will v-for onto the page within an aria-live container.

I have built a full component that will do this in various ways as an example for anyone looking to do the same:

export default {
    props: {
        value: String,
        ariaLive: {
            type: String,
            default: "assertive",
            validator: value => {
                return ['assertive', 'polite', 'off'].indexOf(value) !== -1;
            }
        }
    },
    data() {
        return {
            textToRead: []
        }
    },
    methods: {
        say(text) {
            if(text) {
                this.textToRead.push(text);
            }
        }
    },
    mounted() {
        this.say(this.value);
    },
    watch: {
        value(val) {
            this.say(val);
        }
    }
}
.assistive-text {
    position: absolute;
    margin: -1px;
    border: 0;
    padding: 0;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
}
<template>
    <div class="assistive-text" :aria-live="ariaLive" aria-relevant="additions">
        <slot></slot>
        <div v-for="(text, index) in textToRead" :key="index">{{text}}</div>
    </div>
</template>

This can be used by setting a variable on the parent to the v-model of the component, and any changes to that variable will be read to a screen reader once (as well as any time the parent container becomes tab-focused).

It can also be triggered by this.$refs.component.say(textToSay); -- note this will also be triggered again if the parent container becomes tab-focused. This behavior can be avoided by putting the element within a container that will not receive focus.

It also includes a slot so text can be added like this: <assistive-text>Text to speak</assistive-text> however, that should not be a dynamic/mustache variable or you will encounter the problem in the original question when the text changes.

I've also updated the sandbox posted in the question with a working example of this component.

like image 133
Will P. Avatar answered Sep 19 '22 21:09

Will P.