Accessible page titles in a Single Page App

According to WCAG 2.4.2 pages should have titles. How to go about this in a single page world?

What are titles for?

Pages have titles so that people can recognise where they are. They are like a plaquette on a meeting room door, signposts that mark the platform in a train station or the signage for a supermarket aisle. They help people figure out where they are.

Additionally, page titles also signify uniquely what’s on a URL. This is helpful for search engines, they display the title. Or for social media platforms, they display it when you share a link. People using screenreaders hear the title, it lets them figure out what a page is when they land on it.

Titling pages, the traditional way

In a site with multiple pages, you can put different content in the <title> element for each different page. This is trivial if you build each page separately, it is a little more work when the value comes out of a CMS, but still fairly straightforward.

Titling pages in Single Page Apps

In a Single Page App (SPA), the user never leaves the page. There is no new page with a new title. Instead, you’ll have to update this manually by changing the value or document.title , which is where the page title is in the DOM.

Changing pages in SPAs is often done with routers like react-router and vue-router . I was surprised to see that, by default, those two only update content and the URL, not the document title.

You can update the page title manually, though. In React, you can do it in the componentDidMount() of a route, and there is a react-document-title package that does it for you. If you want to update more meta info than just the title, there is React Helmet.

In Vue, I had luck doing it in beforeEach of the router:

router.beforeEach((to, from, next) => {
  document.title = `${to.name} - Your site`;
  next();
});

But if you’d like to abstract this further and update the page’s title along with other things in the head of your document, there is Vue Helmet or Vue Meta.

(Update 2 June 2020) In Svelte, you can set page titles using the special <svelte:head> element in the components that you use as a route, like so:

<!-- MyPage.svelte -->
<svelte:head>
  <title>Page Title goes here</title>
</svelte:head>

Announcing titles

In screenreaders, when a user goes to a new page, it will read out the title of that page. In a Single Page App world, you can update the title with document.title , but, sadly, that change does not trigger a screenreader announcement. It is helpful to do this manually, for example by putting content into a live region (the on demand live region abstracts this).

There are different strategies as to what to read out on a route. In an SPA, you could choose to set focus to the top of the document when you do a route, this would make it feel like a multi page application. Users would have to use skip links to get back to the content, just like in multi page applications. But maybe you only update one section, and your strategy is to move focus to the new content. In this case, you could ensure the title of whatever is new is read out, rather than the updated page title. For example, if you replace the main by something new and then focus the new content, convey to assistive technologies what the title of the newly focused content is, for example by having its first heading announced.

Does this mean the title is irrelevant in SPAs? Not really, it is still useful to have an up to date title , for example for people switch between tabs or when you turn on Server Side Rendering.

TL;DR

Giving pages unique titles aids accessibility and is compulsory if you are after WCAG 2 AA compliance. If you build a single page app, update the title manually, but also look at having something useful announced when new content is inserted. It could make more sense for this to be a section title than the document’s title, depending on what you’re building.

Thanks Job, Almero and Marco for answering questions and giving early feedback.

Update 2 June 2020: added info for Svelte

Comments, likes & shares (1)