skip to Main Content

I have this content (simplified):

<div>
  $<span class="enlarged-text">245</span>.50
</div>

And when I use CAPSLOCK + Right (read next element) with Voiceover on Safari (MacOS Monterey 12.7.1) it reads it out as "two hundred and forty five dollars and fifty cents", as desired.

However, using the auto-readout feature of voiceover – initiated using CAPSLOCK + A – voiceover quite bizarrely reads out "two dollars, forty five point five zero". Is there a way to fix this?

I’ve tried the undocumented role="text" on the surrounding div and it helps somewhat, but instead reads out as "two hundred and forty five dollars, fifty" using the auto-readout. And using CAPSLOCK + Right it reads as "two hundred and forty five dollars point five zero".

<div role="text">
  $<span class="enlarged-text">245</span>.50
</div>

I also tried surrounding each piece in a separate span, but it made no difference:

<div role="text">
  <span>$</span><span class="enlarged-text">245</span><span>.50</span>
</div>

2

Answers


  1. Since you are experiencing inconsistent behavior with Voiceover’s automatic readout feature, try and Use the aria-label attribute to explicitly specify the text that should be read out by Voiceover (ARIA: Accessible Rich Internet Applications).

    <div aria-label="245 dollars and 50 cents">
      $<span class="enlarged-text">245</span>.50
    </div>
    

    You can also use the aria-hidden attribute to hide the elements that are causing incorrect readouts from the screen reader, and add a visually hidden span that contains the correct format for the screen reader.

    <div>
      <span aria-hidden="true">$<span class="enlarged-text">245</span>.50</span>
      <span class="visually-hidden">245 dollars and 50 cents</span>
    </div>
    

    .visually-hidden is a class that hides the content visually but keeps it accessible to screen readers.
    It would be defined as:

    .visually-hidden {
      position: absolute;
      width: 1px;
      height: 1px;
      margin: -1px;
      padding: 0;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      border: 0;
    }
    

    (From "CSS in Action / Invisible Content Just for Screen Reader Users")

    You might also try using the content CSS property with the ::before or ::after pseudo-elements to inject the currency symbol, which may not interfere with Voiceover’s readout logic.

    <div class="price">
      <span class="enlarged-text">245</span>.50
    </div>
    
    .price::before {
      content: "$";
    }
    

    As noted by QuentinC in the comments:

    Attributes aria-label and aria-labelledby only work on interactive/focusable elements and landmarks, or elements having such roles. So, in this case it won’t work.

    Actually, aria-label can sometimes be used on non-interactive elements to provide an accessible name, but its support can indeed be inconsistent across different screen readers and scenarios.

    The goal remains to make sure the screen reader correctly interprets the dollar amount as a whole number followed by the cents, without being misled by the HTML structure.

    You could try and combine the dollar sign and the number in a single span without visual space, and then use CSS to visually enlarge only the dollar amount part.

    <div>
      <span aria-hidden="true" class="price">$245<span class="cents">.50</span></span>
      <span class="visually-hidden">245 dollars and 50 cents</span>
    </div>
    
    .price .cents {
      font-size: smaller;
    }
    

    Alternatively, if modifying the visual appearance is not an option, you could use JavaScript to dynamically set the aria-label attribute based on the content of the spans when the page loads or when the content changes.

    window.onload = function() {
      var priceDiv = document.querySelector('.price-container');
      var price = priceDiv.innerText;
      priceDiv.setAttribute('aria-label', price.replace('$', '') + ' dollars and ' + price.split('.')[1] + ' cents');
    };
    
    <div class="price-container" role="text">
      $<span class="enlarged-text">245</span>.50
    </div>
    

    The JavaScript approach manipulates the aria-label dynamically, making sure the label is always set to the correct dollar amount that needs to be read out.

    If none of the above solutions work due to the specific quirks of Voiceover, check, as QuentinC suggests, if any existing CSS rules are interfering with the screen reader’s ability to correctly interpret the content.


    It is just a shame, I’d prefer to have the one element that is read by both visual and screen reader users. Otherwise, it is easy for content to get out of sync.

    To mitigate the risk of content getting out of sync, you could use JavaScript to dynamically update the visually hidden text based on the visible content.
    That way, any changes to the visible price would automatically be reflected in the screen reader text.

    Use a visually hidden span in addition to the visible content.

    <div id="price-container">
        $<span class="enlarged-text">245</span>.50
        <span class="visually-hidden" id="accessible-price"></span>
    </div>
    

    Make sure it is accessible to screen readers but not visible.

    .visually-hidden {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        border: 0;
    }
    

    Dynamically set the content of the visually hidden span.

    function updateAccessiblePrice() {
        var priceText = document.getElementById('price-container').innerText;
        var accessiblePrice = priceText.replace('$', '') + ' dollars and ' + priceText.split('.')[1] + ' cents';
        document.getElementById('accessible-price').innerText = accessiblePrice;
    }
    
    // Initial update
    updateAccessiblePrice();
    
    // Update when the content changes (e.g., via AJAX, user action, etc.)
    // That needs to be triggered appropriately depending on how your content updates
    

    That approach would make sure the screen reader text is always in sync with the visible text.

    It requires a bit of extra effort to set up, but it effectively addresses the concern of maintaining consistency between what is read by screen readers and what is seen by sighted users.

    Login or Signup to reply.
  2. Using JavaScript you could make it a bit dynamically but not perfect at all

    window.onload = function() {
      var priceDiv = document.querySelector('.price-container');
      var price = priceDiv.innerText;
      priceDiv.setAttribute('aria-label', price.replace('$', '') + ' dollars and ' + price.split('.')[1] + ' cents');
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search