Announcing document's title changes with aria-live

Important: The content that follows is an experiment with very limited testing. It would need testing on a broader range of browser and screenreader combination before being used in production.

Building a single page applications requires taking over the browser's responsibility for navigating from page to page fr. This means responding to changes to the page URL and showing the page that corresponds, of course. But browsers also broadcast the title of the new page to assistive technologies, making the new location clear to the users. This is also something that needs to be taken care of.

The Accessible Rich Internet Application (ARIA) specification brings the aria-live attribute to let assistive technologies announce updates of the page as they happen. It has good support, so could it be used on the document's title tag? This is a test to find out.

The experiment

This page hosts an experiment to test if the title gets announced OK upon update. Click a button, the title gets updated and hopefully announced.

To witness the announcements, you'll need to have a screen-reader alongside your browser. Both Mac, Windows 10 and Ubuntu come with one built in, and NVDA can be freely installed on Windows.

Before trying to update a fancy element like <title>, let's start by checking everything works as intended with a more common candidate for aria-live: a simple <p> element,for example.

Clicking the next button will update a <p> tag right after. Because it has an aria-live="polite" attribute, this should trigger an annoucement of "Clicks" followed by a number.

Clicks: 0

Now for the actual test, the form that follows will trigger an update of the document's title. If things work, it should trigger the same kind of announcement: "Clicks" followed by a number. The title in the tab will be updated too, but that part is more likely to work.

It offers two ways to update the title, in case it makes a difference (both worked in my limited testing):

How to update the title

Behind the scene

The first step is to add the aria-live attribute to the <title> element. Not sure if having it in the markup when the page loads would trigger a duplicate announcement (with the title already announced by the browser), so I prefered playing is safe and add it via JavaScript.

var titleElement = document
  .head
  .querySelector("title");
// Announcement feels important, so using `assertive`
titleElement.setAttribute('aria-live', 'assertive');

// Same for the `<p>`, except it's not as important so `polite`
document.getElementById('live-div').setAttribute('aria-live','polite')

Only adding the attribute is not enough, though. The <title> element hidden by default and doesn't make its way into the accessibility tree.

Screenshot of Firefox developper tools' Accessibility panel before doing anything with no entry for the title.
Firefox developper tools' Accessibility panel shows no node for the title in the elements of the accessibility tree, only the name of the document.

Using display:block on the <head> and itself will allow it to make its way there.

Screenshot of Firefox developper tools' Accessibility panel showing the title inside the accessibility tree.
With display:block in, Firefox developper tools' Accessibility panel shows a new node for the title in the accessibility tree.

As we don't want it shown on the page, though, we need to use some CSS to hide it accessibly:

head {
  display: block;
}

title {
  display: block;
  /*
    From Bootstrap's visually-hidden mixin
    https://github.com/twbs/bootstrap/blob/main/scss/mixins/_screen-reader.scss#L8
  */
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

With that, any change to the title should get announced. If not all changes needed announcement, toggling either the display of the <title> or the aria-live attribute as necessary should give control. But it would need further testing, past this experiment focusing only on aria-live being recognised.

For the updates, it's a little more JavaScript to listen to events of the form and button, and update the appropriate values:

var value = 0;
var content = "Clicks: " + value;

function updateContent() {
  value++;
  content = "Clicks: " + value;
}
document.addEventListener("click", function(event) {
  // Only consider clicks on buttons
  if (event.target.tagName == "BUTTON") {
    // Grab the target on the page
    var updateTarget = event.target.getAttribute("data-target");
    if (updateTarget) {
      updateContent();
      document.getElementById(updateTarget).innerHTML = content;
    }
  }
});

document.addEventListener('submit', function(event) {
  // Otherwise, the page reloads, whoops!
  event.preventDefault();
  updateContent();
  // Grab which way we should update the title
  if (document.querySelector('[name="updateType"]:checked').value === 'property') {
    document.title = content;
  } else {
    document.head.querySelector('title').textContent = content;
  }
});

Now hopefully that works across all kinds of browsers and screenreaders, but I could only test with Firefox + Orca on Ubuntu and Safari + Voiceover on Mac. If you try it with another combination, I'd be glad to know.

OK, for real, next article will be back to the static site generator.

Updates

23 Jul 2020: Add a link detailing what needs to be handled when taking over the browser navigation with JavaScript. Thanks Yann fr for the link, and Audrey fr for the article!