-
Notifications
You must be signed in to change notification settings - Fork 20
HTML
This document tries to give tips on how to write accessible HTML documents and why. Feel free to contribute and enhance it. This is a collection on little tips or mistakes often seen in production code.
This is just common sense, we won't ship JS syntax error in production, then we need to do the same for HTML.
Even if it's not a full requirement of the WCAG SC 4.1.1, an invalid document may lead to weird errors.
Always validate your document with the nu validator.
<!DOCTYPE html>
or (case-insensitve)
<!doctype html>
Without a proper doctype, a document will be interpreted in quirks mode.
<html lang="en" >
Without a proper lang
attribute, assistive technologies (ATs) will use the operating system's language, that won't necessarily match the document's language. This can lead ATs to read a French website with an English pronunciation engine.
This is a requirement for WCAG 3.1.1
As a sub rule, any part of the document that differs from the main language must be tagged with the corresponding lang
attribute.
WCAG 3.1.2 language of part.
<p>Here is an example <span lang="fr">en français</span></p>
We need to keep in mind that common foreign words don't necessary need to be tagged as such, thus the document doesn't have to be overly verbose.
For exemple:
<p lang="de">Das ist cool!</p>
Even if “cool” is not a German world, we don't need to tag it as it's commonly used, so chances are than a German pronunciation engine will know how to pronounce it correctly.
Even with the corresponding http header, a document must have a charset. When there is no charset, a POST method would be sent in ISO-8859-1, not UTF-8 in HTTP1.1.
e.g.
<meta charset="utf-8">
A title
element is the first element read in any page. If it doesn't reflect the context, an AT user may leave the page or lose all sense of where they are.
That's why it's a WCAG SC 2.4.2 requirement.
This is also usually the text deisplayed in search engine lists, and the document's tab title on our browser of choice
<title>Semantic HTML - APITeam Doc - Talend</title>
See how to write an informative title.
Please note: each page must have a different title
as a way to disambiguate between two pages.
There are many possible values for the viewport meta
, but this one is a good starting point:
<meta name="viewport" content="width=device-width, initial-scale=1">
(This can be translated as: by default, start with the viewport width at your disposal, zoom by a factor of 1.)
Also, there is no valid need to disallow zooming. user-scalable=no
must not be used.
This can be a WCAG 1.4.4 failure and also a user-hostile value.
The element order should, most of the time, match the visual order so that it's not confusing for a keyboard-only user to use the website. When the keyboard navigation is inconsistent this can lead to a WCAG 2.4.3 failure.
When there is a URL to go to, then you must use a link; otherwise use a button.
This seems trivial but keep in mind that many users use ctrl+click or middle click to open a link in a new tab. That's really frustrating when you can't do it because the link is in fact a button.
The only valid usage of <a href="#" […]>
is for a “go back to top” feature, every other usage would suggest to use a button, not a link.
Providing skip links is an easy win for accessibility. When there is no skip link, a user must navigate through all the repetitive content before reaching what they are looking for. You can find an exemple on Manuel Matuzovic blog.
This is also a WCAG 2.4.1 criteria.
An assistive technology can list every link on a given page. This allows AT users to navigate through the list rather than tediously reading the whole page before finding the link they would want to navigate to.
This implies that every link should be properly named (WCAG 2.4.4. If there are multiple links with the same name, they must point to the same target (document or document fragment).
Also, we need to provide context. The worst-case scenario would be to provide many links with only “Learn more” as their label. Presented with such content, AT users won't be able to know what they will learn more, or which link is pointing to what.
An accessible name is the name of an element in the accessible tree. It is a computed value (see below, priority method). Without a proper accessible name, a user may have a hard time finding out what the element's purpose is. This means that every interactive control must have an accessible name (WCAG SC 1.3.5).
Also, every interactive control must have a dedicated role.
When giving a name to an interactive element here is the priority method to follow to label it:
- Native HTML techniques,
-
aria-labelledby
attribute pointing to theid
of one or more elements, - Visibly-hidden content that is still in the page,
-
aria-label
attribute.
Credits go to Adrian Roselli and Léonie Watson
Also, keep in mind that not every element and browser / AT support aria-label
.
The placeholder attribute represents a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value. A hint could be a sample value or a brief description of the expected format. The attribute, if specified, must have a value that contains no U+000A LINE FEED (LF) or U+000D CARRIAGE RETURN (CR) characters. The placeholder attribute should not be used as an alternative to a label.
This is what the html spec say.
Most of the time, br
is used instead of CSS but, per the spec, “br elements must be used only for line breaks that are actually part of the content, as in poems or addresses”.
HTML5: The br
element.
When using a tabindex
attribute, there are only 2 valid values:
- -1 so that the element is not natively focusable with a keyboard but may be dynamically given focus;
- 0 so that the element is focusable with a keyboard.
When we use tabindex
above 0, we indicate the tab order within the document. This is really tricky and generally leads to accessibility issues.
See Karl Groves' article on tabindex.
Keep in mind that the document order should always be as close as possible to the visual order.
An image without an alt
attribute is invalid in HTML and is a failure according to WCAG SC 1.1.1.
The reason is simple. When there is no alt
attribute, some assistive technologies will resort to reading the src
attribute in the hope that it will be somehow relevant — and usually it's not, trust us. Reading a URL aloud is not that interesting, even more so when we add unique IDs.
If the image is purely decorative, an empty alt
is the way to go. The AT should just ignore the image.
If the image provides some info, we need to convey the information and use a sentence without forgetting punctuation.
<img src="foo/bar.png" alt="A cyclist falling on the tramway rails at Mangin station.">
This alt
must convey the meaning of the image: we need to focus on what we want to communicate.
For further (thourough) information on how to write an alt
attribute, please refer to W3C's alt
Decision Tree.
In some contexts, we want to go straight to the point like the example above. But sometimes, we need to provide a longer description for an artistic picture for instance, or for an infomation-rich graph. We can do so through the use of the longdesc
attribute. This attribute provides a way to reference a URL (pointing to a document or a document fragment) that describes the image thoroughly. This can be seen on a photograph on Stéphane Deschamps' blog. Note: you either need to look into the source and find the longdesc
attribute, or use a screen reader; usually they rely the same mechanism as with a link (hit Enter to follow) to open the description resource.
When an element is scrollable (code sample for instance), a keyboard-only user must be able to scroll it with the arrow keys (WCAG SC 2.1.1).
To provide this functionality, the element must be made focusable using tabindex
, and as it's now become an interactive content of sorts it needs a label
and an identifiable role
.
We can either use a section
or a div
with the “region” role
.
<section aria-labelledby="caption01" tabindex="0">
<div role="region" aria-labelledby="caption01" tabindex="0">
You can see this live on Adrian Roselli's “Under-Engineered Responsive Tables”.
Every JavaScript or CSS asset declared on the head
element blocks the main thread by default.
If you really need to add a JavaScript asset in the head
, make sure to add a defer
or async
attribute so that the main thread is not blocked until the script is downloaded, parsed and run.
Every page must have a correct heading structure. This means that every page should have a h1
element and if there is a h3
there is a h2
before.
Think of the headings as a Table of contents. In a document, a book, etc. you wouldn't want to miss levels. This rule is the same for web documents, even if it's a webapp.
There is only one valid use case for the title
attribute: When using a frame
or an iframe
, one must add a title
attribute to give an accessible name to the frame.
All other use cases are mostly useless as the title is only shown on hover.
Please refer to Steve faulkner article on using title attribute for an explanation.
Sectioning HTML elements (nav
, main
, aside
, …) expose ARIA landmarks and help AT users navigate through the page (D shortcut on NVDA) when used correctly.
When misused, those elements can provide a worse experience, as navigating in the page can be too verbose. Use them carefully.
For some time, there was a question as to whethre the main element could be declared multiple times (in the whatwg).
From an accessibility point of view, this is misleading when AT users try to navigate from landmark to landmark.
If there is more than one nav
element, give an accessible name to each of them so that there is no confusion and this helps to quickly understand the navigation element's purpose.
When HTML5 began, many web developers misused the section
element and just replaced div
with section
.
This forced a change in the ARIA spec so that the section
element would not expose an aria role unless it was properly labelled, because websites became so verbose that they were unusable with AT.
Also, we need to keep in mind that a section would trigger a change in the browser style of h1
element. This is the legacy of the document outline algorithm (which ended up not being implemented anywhere so far).
When in doubt, refer to Bruce Lawson article on the section element.
Even in a web app context, a form
element gives us some benefits such as submitting on pressing the Enter key, triggering the validation API, creating a FormData object by default, …
We need to keep in mind that per spec, the default button type is submit inside a form is submit
not button
.
CSS changes the accessible tree. It seems strange at first glance but this is logical.
When an element's display
property is set to none
, the web author doesn't want to communicate those elements to the end user, so the accessible tree would have to reflect that.
There are some other tricky parts:
-
display: content
can sometimes remove the semantics of elements. This is a bit less true these days, but it's still tricky to use and needs testing. - Some
display
values can remove the “table” role on thetable
element. - With VoiceOver, if the
list-style
property is set tonone
, the “list” role will not be conveyed to the user. Scott O'Hara documented this change.
ARIA (Accessible Rich Internet Applications) is a complementary W3C specification that enriches the HTML specification to provide roles, states, and properties to improve accessibility when native elements are not enough.
Even if ARIA is a win over the accessibility community, if it's wrongly used it can do more harm than good.
In the last WebAIM Million analysis, it turns out that home pages with ARIA present an average 60% more errors than those without!
The first rule of ARIA is to always prefer a native HTML element rather than using the corresponding ARIA role.
Don't:
<div role="banner"></div>
Do:
<header></header>
Also, keep in mind that adding a redundant role is redundant and error-prone.
Don't:
<header role="banner"></header>
Do:
<header></header>
Native semantic are here for a reason.
For example, we want to expose a heading as a tab, but adding a role
attribute on h2
would remove its “heading” role and thus it won't be exposed as a heading.
Don't:
<h2 role="tab">heading tab</h2>
Do:
<div role="tab"><h2>heading tab</h2></div>
Don't:
<div onClick="">
Stephane for Thain: you had added <div role="button" tabindex="0" onClick="">
in “Don't” but there may be times when it's the only option, and technically it works. So I'm not sure it's a “Don't” per se.
Do:
<button type="button" onClick="">
Don't:
<button aria-hidden="true" type="button" >press me</button>
<div aria-hidden="true"> <button type="button" >press me</button></div>
(If the elements are hidden from the accessible tree, they will not be seen by ATs.)
If an interactive element is used without a proper accessible name, AT users won't be able to know the purpose of this element. For exemple:
<button type="button"><i class="icon icon--add"/></button>
A sighted user would be able to understand that this button adds a new element. An AT user will only hear “Button” and won't be able to know what it does.
As has already been said, every asset requested in the head
element is blocking the main thread.
One way to speed up the website is to only request the CSS that style elements above the fold and asynchronously request the rest of the CSS later on.
While http/2 lets us query a server without having to negotiate the connexion every time and there is no 5 parallel requests limit, every request is handled by browsers sequentially. So even in http/2 it's still a good thing to limit the number of requests per asset.