Web Accessibility

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 is a great way to learn about accessibility by example.

Table of Contents

POUR Principles

Ensure content is always:

  • Perceivable. Available to sight, hearing, and/or touch.
  • Operable. Usable forms, controls, and navigation.
  • Understandable. UI shows intent.
  • Robust. ie. Assistive technologies friendly.

Usability

  • Support keyboard navigation, and interaction as much as on the mouse.
  • Show user location when using keyboard navigation.
  • Add links to opt-out keyboard navigation on certain sections. eg. nav bars.

Structured Pages

NOTE Code examples are in Slim to keep the focus on the elements, attributes, and values rather than HTML’s verbosity.

  • Use HTML5 semantically.
  • Set descriptive title meta tag for user agents.
  • section groups elements into units of information.
  • Use div primarily for:
    • Grouping what should be styled together
    • Enforcing layouts, instead of being their foundation.
  • header for page headlines, and occasionally for sections.
  • 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.
  • Style headings(hX), don’t skip them.
  • Set text tone with
    • strong to add / show importance.
    • em for emphasis. ie. tone.
  • Id content in different languages with the lang attribute.
  • Style text inline.
    • 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.

Images

  • Only add relevant alternative info. eg. don’t describe logos.
  • The alt attribute only works on img, area, input.
  • Use aria-label to add alternative info anywhere.
  • Set purely decorative images as CSS backgrounds.
  • Use aria-hidden attribute for decorations such as icon fonts.
  • When referring to images from the text we can use figure, and optionally figcaption to describe the diagram.

alt text

alt text is a description of an image for those who can’t see it. Hence,

  • Describe the image.
    • don’t SEO it.
    • no place for attributions.
  • alt text depends on context.
  • Keep it short.
  • Don’t start with ‘image of’, or ‘photo of’. Screen readers already say it.
  • End with a period.

Even when no alt text is needed we need to use it with an empty attribute for:

  • repeated images. eg. profile pictures in a feed.
  • icons with text labels.
  • linked images with caption. ie. newspaper style.

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.

Tables

  • Use CSS grid instead of tables for tabular layouts.
  • Avoid nesting tables.
  • caption tables to associate them with their descriptions.
  • Set header scope.
  • Use proportional (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

Forms

  • Ensure they’re keyboard accessible.
  • Organize and label fields clearly.
  • fieldset, section, and div delimit form space.
  • Associated elements have matching id, and for.
  • Associate labels extend selection area.
  • Use fieldset with legend for specificity.
  • Avoid <select multiple> menus.
  • Avoid empty:
    • value attributes.
    • button contents.
  • Don’t replace labels with placeholders.

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"

Inputs

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.

Common Attributes

Mandatory form fields:

input[type="text" required]
Types

Check out W3 School’s input types list for a the complete set of input types and attributes.

Text & Patterns

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 Linking

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. (hrefs omitted for simplicity)

Access

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.

Attributions

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"

Privacy

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 without damaging the site’s SEO:

  • Use nofollow for:
    • Sites whose content we usually don’t refer to.
    • Non-endorsed sites.
    • Paid referrals.

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 or flexbox, if needed. That’s beyond the scope of this cheat sheet, though.

Fancy Meets Accessible

Dynamic Content

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;
}
  • Ensure hidden content announces itself upon display.
  • When necessary, ensure dynamic content is keyboard accessible.

Colors

  • Avoid using only color to convey information ie. single green/red dot.
  • Set the contrast ratio between elements so everyone can distinguish them.
    • Rely on contrast ratio calculators.

Online contrast ratio checkers:

Accessible color combinations:

Resources

Tools & Code

References