When considering an application's page or view structure, there are a few primary concerns that should be planned for:
- page title
- skip navigation links
- focus management
Page Title
Each page (what is rendered for a unique URL) should have a page title. This page title should be unique to that page, and should accurately reflect what that page does.
Consider this format:
Unique Page Title - Site Title
Note that the unique page title is first. This is because it is the most important piece of information from a contextual perspective. Since a user with a screen reader can interrupt the screen reader as they wish, it introduces less fatigue when the unique page title is first, but provides the additional guidance if it is desired.
One simple way to add page titles is to create a title
helper:
import Helper from '@ember/component/helper';
export default class Title extends Helper {
original = document.title;
compute([title]) {
document.title = title;
}
willDestroy() {
document.title = this.original;
}
}
We can use this helper to set the page title at any point in any template.
For example, if we have a “posts” route, we can set the page title for it like so:
Extending the example, if we have a “post” route that lives within the “posts” route, we could set its page title like so:
This title will take effect when we enter the “post” route, and the line of code in our willDestroy
hook will take care of restoring the former title when we return to the “posts” route.
This technique is a reasonable first step, but has limitations:
- It doesn't work when the page is rendered server-side with FastBoot.
- It doesn't provide any conventions for constructing nested page titles.
- It doesn't automatically apply the site title (though you can imagine how to add that).
- It may not be absolutely robust if data in your app changes a lot (imagine a real time app).
When your needs become more complex, the following addons facilitate page titles in a more dynamic and maintainable way (including FastBoot support):
To evaluate more addons to add/manage content in the <head>
of a page, view this category on Ember Observer.
You can test that page titles are generated correctly by asserting on the value of document.title
in your tests:
import { module, test } from 'qunit';
import { visit, currentURL } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
module('Acceptance | posts', function(hooks) {
setupApplicationTest(hooks);
test('visiting /posts', async function(assert) {
await visit('/posts');
assert.equal(document.title, 'Posts - Site Title');
});
});
Skip Navigation Links
A skip navigation link, or skip link, is a useful feature for users who want to bypass content that is repeated on multiple pages (i.e., a site header). This can especially helpful to users with assistive technology, who have to browse website content in a more linear fashion, but it can also be useful for power users who prefer to navigate websites only using a keyboard.
To implement a skip link in an application, add an anchor element as close as possible after the opening <body>
element, and have it link to the beginning of the page's main content area.
To read more about skip links, visit the MDN docs.
Focus Management
No single-page application framework provides the appropriate route-level focus for assistive technology in a default manner. This is primarily due to the way pushState
works, and the lack of an event hook for JavaScript frameworks to tell assistive technology that the page content has changed. This also means that the focus is unchanged on route navigation, which in some cases means that it would be lost entirely (if the element that previously had focus is now gone).
Most frameworks have some mechanism for adding the missing functionality to an application. In Ember, there is an attempt to address these two specific shortcomings with RFC 433; in the meantime there are a few addons that exist to help provide page or view-level focus for your application. All options should be evaluated to determine which is the appropriate use case for your application: