description: > Everyday accessibility for HTML coders.
Make it accessible.
Make it fancy.
Make sure the fancy doesn't break accessibility.
— Morten Rand-Hendriksen
TLDR; See how accessibility helps improve all user's experience. WAI's "bad" demo{:rel="nofollow noopener noreferrer"} is a great way to learn about accessibility by example.
Table of Contents
Ensure content is always:
nav
bars.NOTE Code examples are in Slim{:rel="nofollow noopener noreferrer"} to keep the focus on the elements, attributes, and values rather than HTML's verbosity.
title
meta tag for user agents.section
groups elements into units of information.div
primarily for:
header
for page headlines, and occasionally for section
s.nav
for major navigation links for a site. Often a header
or aside
child.footer
for info about a page, or section
eg. copyright.aside
Independent info related to the surrounding content. eg. sidebar, pull-quotes.section
Content-focused element for grouping independently consumable parts of a page.article
Self-contained content that could be consumed independently from a page.h
X), don't skip them.strong
to add / show importance.em
for emphasis. ie. tone.lang
attribute.mark
to highlight content.small
for small print.abbr
for abbreviations.span
for any other styling. eg. icon fonts. (i
doesn't stand for icon.)Beware that some elements, such as abbr
we'll need to set complimentary
attributes to make full use of them.
<abbr title="Accessible Rich Internet Applications">ARIA</abbr>
Although, the title
property on abbr
is well supported on by screen readers,
we shouldn't rely on it for any other elements.
In general, a page's header and headings should make a nice table of contents.
(Comments beginning with //
refer to the line below.)
doctype 5
html lang="en" / Language codes linked in the references
head
meta charset="UTF-8" / Help display text properly
// Use only when site is responsive
meta name="viewport" content="width=device-width, initial-scale=1.0"
// Tell Microsoft's browers we expect them to behave like others
meta http-equiv="X-UA-Compatible" content="IE=Edge"
title Short & Meaningful for screen readers
meta name="description" content="This text shows up in search engines"
body
header role="banner" / Role for main header only
nav role="navigation"
main role="main"
section
article
header
h1 Title
form role="form"
/ ... forms are covered below
p
| This is how we'll
em.tone-class
| add
strong
| text
| in
span#template-id
| Slim
aside role="complementary"
form role="search" / Role for search boxes only
footer role="contentinfo" / Role for main footer only
Since landmark roles are essential for keyboard navigation we included the most basic set in the code sample above.
Although there is apparent redundancy for main
and form
it's actually meant to
reinforcing semantics. For instance, main
role
is a non-obtrusive alternative to "skip to main content" links
.
It's not exclusive of the main
html element.
alt
attribute only works on img
, area
, input
.aria-label
to add alternative info anywhere.aria-hidden
attribute for decorations such as icon fonts.figure
, and
optionally figcaption
to describe the diagram.alt
text is a description of an image for those who can't see it. Hence,
alt
text depends on context.Even when no alt
text is needed we need to use it with an empty attribute for:
Invoke purely decorative images, such as backgrounds, in CSS to avoid using alt
altogether.
SVG images can be called using an img
tag, in which case we can use the alt
text to describe it. When, for reasons, we need to embed the image directly into
and svg
element we'll need to use role="img"
, and aria-label="alt text here"
to make it accessible.
caption
tables to associate them with their descriptions.rem
or em
) rather than absolute sizing.thead
, tfoot
, and tbody
group cells semantically.tr
, th
, td
make data navigation easier.table
caption
| Shopping List
thead
tr
th scope="col"
| Description
th scope="col"
| Price
th scope="col"
| Quantity
tbody
tr
th scope="row"
| Phone
td 5
td 1
tr
th scope="row"
| Computer
td 8
td 2
tfoot
tr
th scope="row"
| Total
td 21
td 3
fieldset
, section
, and div
delimit form space.id
, and for
.label
s extend selection area.fieldset
with legend
for specificity.<select multiple>
menus.value
attributes.button
contents.label
s with placeholder
s.A few ways we can associate form elements:
form id="pizza-order" role="form"
fieldset
legend
| Toppings:
input id="ham" type="checkbox" name="toppings" value="ham"
label for="ham"
| Ham
input id="pepperoni" type="checkbox" name="toppings" value="pepperoni"
label for="pepperoni"
| Pepperoni
input id="mushrooms" type="checkbox" name="toppings" value="mushrooms"
label for="mushrooms"
| Mushrooms
input id="olives" type="checkbox" name="toppings" value="olives"
label for="olives"
| Olives
label for="city"
| Choose your delivery city
select id="city" name="delivery-city"
optgroup label="Asia"
option value="HK"
| Hong Kong
option value="TK"
| Tokyo
optgroup label="Europe"
option value="AM"
| Amsterdam
option value="BA"
| Barcelona
optgroup label="North America"
option value="MX"
| Mexico City
option value="NY"
| New York
optgroup label="South America"
option value="SP"
| Sao Paulo
input for="pizza-order" type="submit" name="pizza-order" value="Order"
input for="pizza-order" type="reset" name="cancel" value="Cancel"
Note:
Most browsers support autocomplete
for various input
elements. While this may
arguably be good from an accessibility standpoint, keep in mind it isn't from a
privacy, and security perspective.
Mandatory form fields:
input[type="text" required]
Check out W3 School's input types list{:rel="nofollow noreferrer noopener"} for a the complete set of input types and attributes.
We can add simple validation patterns to type="text"
inputs. Most common browser
style failure to match the require pattern.
input[
type="text"
pattern="^(\d{3}-?\d{4})$"
]
These validations are meant to improve usability, not security. If security is a concern we must always do it at the server level.
Some common patterns are:
// Generic text. No special characters
input[
type="text"
pattern="[a-zA-Z0-9]+"
]
// Username. 2-20 characters long
input[
type="username"
pattern="^[a-zA-Z][a-zA-Z0-9-_\.]{1,20}$"
]
// Password. Upper, lower cases, numbers, special characters, min 9 chars
input[
type="password"
pattern="(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$"
]
For security reasons, never style type="password"
other than when missing
in register forms.
Web pages are linked through a
elements. We can tell user agents such as bots,
browsers, and screen readers, how a website relates to ours through the rel
attribute. Here's a basic list. (href
s omitted for simplicity)
a rel="contents" / as in TOC
a rel="home"
a rel="first"
a rel="last"
a rel="prev"
a rel="glossary"
a rel="help"
a rel="alternate" / page's alt delivery mechanism eg. Atom feed.
a rel="author"
a rel="license content-license" / link to data license
a rel="content-repository" / link to data store
a rel="code-license"
a rel="code-repository"
a rel="noopener noreferrer"
a rel="nofollow"
a rel="privacy-policy"
a rel="terms-of-service"
The first setting,noopener
noreferrer
, helps protect users from tabnabbing attacks{:rel="nofollow noreferrer noopener"}
without damaging the site's SEO{:rel="nofollow noreferrer noopener"}:
nofollow
for:
Considering semantic HTML, landmark roles, and web linking, a simple main navigation bar could look like:
nav role="navigation"
ul
li
a[
rel="noopener noreferrer"
href="https://avoid.empty.href.com"
]
| 'a' elements shouldn't be empty, ever.
li
a[
rel="nofollow noopener noreferrer"
href="https://non.endorsed.page.com"
]
| Profile in unrelated site
li
a[
rel="noopener noreferrer"
href="/about"
]
| noopener noreferrer are for visitors' benefit
The use of ul
, and li
elements is merely as an example. Nowadays, is easy to
control navigation bar's layout with
CSS grid{:rel="nofollow noreferrer noopener"}
or
flexbox{:rel="nofollow noreferrer noopener"},
if needed. That's beyond the scope of this cheat sheet, though.
Hide anything visually, as well as from screen readers, and other user agents:
.a { visibility: hidden }
.b { display: hidden }
.c { display: none }
Avoid hidding HTML elements through the hidden
attribute. It creates a dependency
on ECMAScript (JS), which not all user might have access to, specially on mobiles.
Hide anything only visually by:
.tucked-away {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}
.camouflaged {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
opacity:0;
}
.unapparent {
position: absolute;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
margin: -1px;
padding: 0;
}
Online contrast ratio checkers:
Accessible color combinations: