Search code examples
javascriptandroidwebviewaccessibilitytalkback

Triggering Android TalkBack Text Programmatically Via A WebView


So I've been working with making an Android WebView with some text in it be more accessible to blind users and I wondered if it was possible to trigger Android TalkBack to speak some text automatically?

I'm thinking a link like this:

<a href="#" id="flubble">Flubble</a>

And another element like this:

<a href="#" id="bibble">Bibble</a>

Then using a focus event to auto read the text in the link:

document.getElementById("flubble").addEventListener("click", function(event) {
    event.preventDefault();
    document.getElementById("bibble").focus();
    //I want it to say "bibble" here
});

Essentially, when it focuses, I would like it to speak the text? Is there any way to accomplish this "Auto TalkBack" functionality?

Cheers <3


Solution

  • Short Answer

    Use a visually hidden aria-live region and copy the relevant text into that region. Additionally use a <button> not a <a href="#" for interactive content.

    Long Answer

    Messing with the user focus without a defined purpose (such as focusing the close button in a modal when it opens)...bad idea, the last thing you want to do is make a user lose their place on the page.

    Additionally this would mean you would need to add a load of tabindex="-1" attributes throughout the page on any element you want to read out that doesn't normally receive focus, making maintainability a nightmare.

    If you want to have text read out then may i suggest you use an aria-live region.

    aria-live on an element is designed to speak out any content that is contained within that element out loud. This happens every time the element's content gets updated.

    You will notice I added aria-live="polite" as that makes sure they announcement doesn't interrupt other announcements on the page.

    For completeness there is also aria-live="assertive" (but you should only really use that for things like warnings and errors / status updates as it will cut off any currently spoken sentence) and aria-live="off" (which is useful for disabling your talk back feature as it is effectively the same as having no aria-live on the element)

    What I would recommend is that you copy the text out of the corresponding element you want read out and into a visually hidden aria-live region.

    In case you aren't aware visually hidden text is screen reader accessible text that is not visible on the screen. Although there are many libraries out there with a sr-only class built in I would encourage you to use the visually hidden class in the fiddle below due to better compatibility and future proofing as I explained in this answer

    I also demonstrated the concept for how to make it reusable across your web view, apologies for the use of jQuery, it's late here and I wanted to put the fiddle together quickly for you!

    Try the below in a screen reader, I think the code is self explanatory but any questions just ask. Finally depending on your "verbosity" settings the third button shows that things get announced the same (with semantic meaning such as "heading" and "link") if you copy those into the aria-live region...if you don't hear these your settings may need changing.

    example

    $('button').on('click', function(e){
        console.log($(this).data('target'));
        
        var targetID = $(this).data('target');
        var text = $('#' + targetID).html();
        console.log(text);
        $('.speak-aloud').html(text);
        
    });
    .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 */
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div class="visually-hidden speak-aloud" aria-live="polite"></div>
    
    <button data-target="p2">Read text in paragraph 2</button>
    <button data-target="p4">Read text in paragraph 4</button>
    <button data-target="longer-text">Read the section contents</button>
    
    <p>This is some other text</p>
    <p id="p2">This is the text to be read aloud in paragraph 2</p>
    <p>Some more filler text</p>
    <p id="p4">paragraph 4, will be read aloud when the second button is clicked.</p>
    
    
    
    <section id="longer-text">
    <h2>Section Header</h2>
    <a href="https://google.com">Go to google link for an example of more complex text</a>
    </section>

    A final word on semantics based on your example.

    For accessibility - semantics matter.

    Anchors should be used for a page change (or in a SPA a URL change if the whole thing is AJAX powered), buttons should be used for interactive content on the same page. So use a <button> instead of <a href="#"> as that lets a screen reader user know what to expect (if I click a link I expect a page change, if I press a button I expect some form of interactive content).