Search code examples
accessibilitywai-ariascreen-readersnvda

How to make nvda read some status message on action?


The issue is I want to make NVDA read some message after pressing a button. For example, there is a button "add new row" which adds new row to a grid. User who uses NVDA screen reader presses Enter on the button and then I want to notify him about the successful result (row has been added). Another example I have a form where user makes some changes. "Save" button is available on the form - after preesing I want to notify the user "changes has been saved". Is there any way to do this without throwing a toaster message? I want to add that in my case after pressing the buttons, focus stays on them.


Solution

  • You are looking for the aria-live attribute

    This allows you to provide updates to a screen reader user by changing the text inside the container that has aria-live on it.

    For your use case you want to use aria-live="polite", aria-live="assertive" should only be used for time sensitive information such as alerts for being logged out etc.

    An aria-live region that is not visible

    You do not have to have a visible aria-live region, but it must be accessible by a screen reader.

    For that we can make it visually hidden using my visually hidden class (which is better than sr-only and is future proofed as clip has been deprecated)

    .visually-hidden { 
        border: 0;
        padding: 0;
        margin: 0;
        position: absolute !important;
        height: 1px; 
        width: 1px;
        overflow: hidden;
        clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
        clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
        clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
        white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
    }
    <div class="visually-hidden" aria-live="polite">
    
    Whatever you put in here via JavaScript will get read out to screen readers as an appropraite quiet time. This is not visible.
    
    </div>
    
    <p>This is visible</p>

    Example from MDN

    As link only answers are never good I have included the example from the linked article on MDN.

    const PLANETS_INFO = {
      mercury: {
        title: 'Mercury',
        description: 'Mercury is the smallest and innermost planet in the Solar System. It is named after the Roman deity Mercury, the messenger to the gods.'
      },
    
      venus: {
        title: "Venus",
        description: 'Venus is the second planet from the Sun. It is named after the Roman goddess of love and beauty.'
      },
      
      earth: {
        title: "Earth",
        description: 'Earth is the third planet from the Sun and the only object in the Universe known to harbor life.'
      },
      
      mars: {
        title: "Mars",
        description: 'Mars is the fourth planet from the Sun and the second-smallest planet in the Solar System after Mercury. In English, Mars carries a name of the Roman god of war, and is often referred to as the "Red Planet".'
      }
    };
    
    function renderPlanetInfo(planet) {
      const planetTitle = document.querySelector('#planetTitle');
      const planetDescription = document.querySelector('#planetDescription');
      
      if (planet in PLANETS_INFO) {
        planetTitle.textContent = PLANETS_INFO[planet].title;
        planetDescription.textContent = PLANETS_INFO[planet].description;
      } else {
        planetTitle.textContent = 'No planet selected';
        planetDescription.textContent = 'Select a planet to view its description';
      }
    }
    
    const renderPlanetInfoButton = document.querySelector('#renderPlanetInfoButton');
    
    renderPlanetInfoButton.addEventListener('click', event => {
      const planetsSelect = document.querySelector('#planetsSelect');
      const selectedPlanet = planetsSelect.options[planetsSelect.selectedIndex].value;
    
      renderPlanetInfo(selectedPlanet);
    });
    <fieldset>
      <legend>Planet information</legend>
      <label for="planetsSelect">Planet:</label>
      <select id="planetsSelect" aria-controls="planetInfo">
        <option value="">Select a planet&hellip;</option>
        <option value="mercury">Mercury</option>
        <option value="venus">Venus</option>
        <option value="earth">Earth</option>
        <option value="mars">Mars</option>
      </select>
      <button id="renderPlanetInfoButton">Go</button>
    </fieldset>
    
    <div role="region" id="planetInfo" aria-live="polite">
      <h2 id="planetTitle">No planet selected</h2>
      <p id="planetDescription">Select a planet to view its description</p>
    </div>
    
    <p><small>Information courtesy <a href="https://en.wikipedia.org/wiki/Solar_System#Inner_Solar_System">Wikipedia</a></small></p>