Skip to content

manage focus, and use aria-live to announce navigation #307

@Rich-Harris

Description

@Rich-Harris

sveltejs/sapper#1083, basically.

There are two separate issues to consider in order to make client-side routing accessible:

1. Managing focus

In a server-rendered app, any time you navigate to a page, the <body> is focused. In Sapper and SvelteKit apps, we blur the current activeElement upon navigation, but this doesn't actually reset the focus, which is what we need — it just removes it temporarily. As soon as you press the tab key (or shift-tab), focus moves to the element after (or before) whichever element was focused prior to the navigation, assuming it still exists in the DOM. This is not desirable.

Went down a bit of a rabbit hole trying to understand current recommendations:

  • Marcy Sutton advises focusing a 'skip back to navigation' link (i.e. a skip nav link in reverse)
  • Ryan Florence thinks the app developer should have control over where focus lands, but that it should be an element inside the innermost route that has changed
  • Nick Colley and Daniel Nixon prefer finding some primary focus target, e.g. the <h1>
  • David Luhr suggests resetting focus to the top of the DOM

I'm sure there are other suggestions too. Each comes from an accessibility expert and has a solid argument, but ultimately they are mutually exclusive. Any would be better than the status quo though.

My inclination in the short term is to simply focus <body> (i.e. David Luhr's suggestion) — it's easy to implement, and matches the behaviour of server-rendered apps. In the future perhaps we could investigate alternatives, particularly ones that put power in app developers' hands rather than making one-size-fits-all decisions for them.

The short term solution ought to be fairly straightforward — we just change this...

if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}

...to this:

document.body.setAttribute('tabindex', '-1');
document.body.focus();

(Better still, only set the tabindex once, when the router initialises.)

2. Announcing the page change

Per the recommendation in https://www.gatsbyjs.com/blog/2019-07-11-user-testing-accessible-client-routing/, we should add an ARIA live region that announces new pages. That looks something like this:

<div
  id="svelte-announcer"
  class="visually-hidden"
  aria-live="assertive"
  aria-atomic="true"
>Navigated to {title}</div>

This could live inside the auto-generated root.svelte component.

It makes sense for title to simply reflect document.title, I think, rather than introducing some complicated way to customise it, since a page already needs an informational document title for SEO and accessibility purposes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions