javascripthtmlaccessibilitytab-ordering

How do I skip a button in tab order and in screen reader cursor when adding content?


I'm creating interactive fiction in HTML. The user is presented with a list of paragraphs and then a series of choices and when they select a choice, the list of choices is removed and replaced with the new content. But, the selected choice is left in as a reminder and to provide a way for the user to return and make a different choice.

Here's the markup before the user selects a choice:

<div aria-live="polite">
 <p>You enter the castle.</p>
 <p><button>Go to throne room</button></p>
 <p><button>Go back out</button><p>
</div>

And here's the markup, edited by jQuery, after the user chooses "Go to throne room":

<div aria-live="polite">
 <p>You enter the castle.</p>
 <p class="reminder">Go to throne room (<a>click this to make a different choice</a>)</p>
 <p>There is a king in the room!</p>
 <p><button>Assassinate him</button></p>
 <p><button>Talk to him</button></p>
</div>

Now, when I press Tab to move to the next element, the link "click this to make a different choice" gets selected which is almost never going to be useful. In the most common case, the user would want to select between the options in the next choice, so I would like the first Tab after choice to select "Assassinate him".

Similarly, after a choice, NVDA starts reading the reminder text first, which I would also like to skip, and have it start reading "There is a king in the room!" instead, since the user already knows what choice they made.

I don't want to remove the text and link from tab order and screen reader completely, just skip it once when the content is created.

Can this be done?


Solution

  • You may want to look at structuring the page differently.

    aria-live should be used for announcements that are out of the document flow (things like alerts, save confirmations, updates etc.)

    For your purposes you do not need aria-live but instead want to implement focus management.

    So your page flow would be as follows:-

    The second point is the key point, you probably want to focus something that is not normally focusable (and you do not want it in the document focus order.). To do this you combine tabindex="-1" and .focus().

    tabindex="-1" basically allows something to received programmatic focus (using your JavaScript .focus() function) but it will not get added to the focus order of the page (you cannot use Tab to focus it).

    I have put together a very ugly demo below to demonstrate the behaviour (I haven't included the change choice etc. but you should get the idea):

    var mainSection = document.querySelector('main');
    var previousActions = [];
    var latestSection = "";
    var getCurrentActions = function(){
        latestSection = document.querySelector('section:last-of-type');
        var actions = latestSection.querySelectorAll('.actions button');
        for(x = 0; x < actions.length; x++){
           actions[x].addEventListener('click', performAction);
        }
        return actions;
    }
    
    var performAction = function(){
    
       
    
       //grab the selected action to later change the actions section.
       var selectedAction = this.innerHTML;
       var lastSection = latestSection;
       previousActions = getCurrentActions();
      
       
       var thisAction = this.getAttribute('data-action');
       if(thisAction == ""){
          alert("you didn't think I was going to do a whole story did you?");
          return;
       }
       var template = document.querySelector('template[data-template=' + thisAction + ']');
       
       var templateHTML = template.innerHTML;
       
       
       for(x = 0; x < previousActions.length; x++){
           previousActions[x].removeEventListener('click', performAction);
        }
       
       mainSection.insertAdjacentHTML('beforeend', templateHTML);
       getCurrentActions();
       
       document.querySelector('#' + thisAction).focus();
       
       var actionReminder = lastSection.querySelector('.actions');
          
       actionReminder.innerHTML = '<p class="reminder">You chose:(<button class="changeChoice">Make a different choice</button>)</p>';
       
       //from here you need to add event listeners to change the choice, remove sections you added if they change their choice etc. The above is purely for demonstration purposes and I would recommend using arrays of information to construct things etc.
       
       
    }
    
    
    getCurrentActions();
    template{
       display: none;
    }
    <main>
        <section aria-labelledby="enteringCastle">
            <h2 id="enteringCastle" tabindex="-1">You enter the castle.</h2>
            <p>The castle is grand and filled with ornate paintings etc. etc. (extended description for immersion if needed)</p>
            <div class="actions">
                <ul>
                    <li><button data-action="throne">Go to throne room</button></li>
                    <li><button data-action="leave">Go back out</button></li>
                </ul>
            </div>
        </section>
    </main>
    
    
    
    <template data-template="throne">
        <section aria-labelledby="throne">
            <h2 id="throne" tabindex="-1">You enter the throne room.</h2>
            <p>There is a king in the room!</p>
            <div class="actions">
                <ul>
                    <li><button data-action="assassinate">Assassinate Him</button></li>
                    <li><button data-action="talk">Talk To Him</button></li>
                </ul>
            </div>
        </section>
    </template>
    <template data-template="leave">
       <section aria-labelledby="leave">
            <h2 id="leave" tabindex="-1">You left the castle</h2>
            <p>You find yourself in a beautiful field, full of daisies</p>
            <div class="actions">
                <ul>
                    <li><button data-action="pick">Pick a Daisy to display on your armor</button></li>
                    <li><button data-action="burn">You remembered that you hate daisies, burn them all!</button></li>
                </ul>
            </div>
        </section>
    </template>
    <template data-template="assassinate">
       <section aria-labelledby="assassinate">
            <h2 id="assassinate" tabindex="-1">You try to assassinate the king</h2>
            <p>Despite his age the king is fast, he parries your attack and holds a knife to your throat.</p>
            <div class="actions">
                <ul>
                    <li><button data-action="">Say you are really sorry</button></li>
                    <li><button data-action="">Deliberately soil yourself, hoping the smell will distract the king</button></li>
                </ul>
            </div>
        </section>
    </template>
    <template data-template="talk">
        <section aria-labelledby="talk">
            <h2 id="assassinate" tabindex="-1">You talk to the king</h2>
            <p>The king tells you of a magical cup that he would reward you handsomely for if you could get it</p>
            <div class="actions">
                <ul>
                    <li><button data-action="">Go find the cup!</button></li>
                    <li><button data-action="">Ask for more information, only an idiot would go after treasure without all the information</button></li>
                </ul>
            </div>
        </section>
    </template>
    <template data-template="pick">
       <section aria-labelledby="pick">
            <h2 id="pick" tabindex="-1">You pick a Daisy</h2>
            <p>You hear a booming voice "who the f**k do you think you are, leave my garden immediately"</p>
            <div class="actions">
                <ul>
                    <li><button data-action="">Draw your sword ready</button></li>
                    <li><button data-action="">Apologise immediately and then spin round</button></li>
                </ul>
            </div>
        </section>
    </template>
    <template data-template="burn">
       <section aria-labelledby="burn">
            <h2 id="burn" tabindex="-1">You Start a fire</h2>
            <p>You realise that this was probably a bad idea, a giant ogre is running straight for you screaming! You see a bucket near a stream</p>
            <div class="actions">
                <ul>
                    <li><button data-action="">Grab the bucket and fill it with sand, ready to attack the ogre</button></li>
                    <li><button data-action="">Grab the bucket and fill it with water, ready to help put the fire out!</button></li>
                </ul>
            </div>
        </section>
    </template>