Writing accessible code

HTML / structured content

Semantic HTML gives us lots of accessibility features for free. Browsers and assistive technology know what to do with the markup. Here is some general guidance to follow:

  • Validate the HTML.
  • Use the correct element - only use a div or span if no other element fits the purpose. Don’t pick an element for purely presentational purposes.
  • Add a skip link for keyboard users as the first thing on the page, pointing at main.
  • Add the following elements as direct children of body: <header>; <main>; <aside>; <footer>.
  • Add <nav>s (with a ul of links inside).
  • Use heading elements (<h1> to <h6>) and do not skip any levels (e.g. going from <h1> to <h3>).
  • Use <section>s to group content thematically, each with a header.
  • Use <article>s for self-contained things like a blog post or a single product, each with a header.
  • Mark tables up correctly (and don’t use them for layout purposes). They should have a <caption>, <th>s for the table headers that have scope attributes for row or column (as appropriate).
  • Add a lang attribute to content not in the main language of the document.
  • Add ARIA attributes carefully - use only semantic HTML where possible. See W3C's Using ARIA for more.
  • Check that accessible names are consistent with visible names, if they’re set separately.
  • Decorative images should have alt="" or be applied with CSS as a background-image.
  • Have a site map, search function, or link rels.

Custom elements

Take extra care with custom elements. View the accessibility tree and make sure that custom elements have the following:

  • Name (this may already be set from a label)
  • Role (see ARIA Design Pattern Examples)
  • Current value (may already be set from an input)
  • Has :focus styles
  • Has an element with aria-live, if required

Forms

Here is some guidance to follow when coding forms:

  • Every form control must have an accessible name associated with it. Pick just one of these methods:
    • A label with for="id-of-the-control" (preferred method)
    • An aria-label="The label for the form" on the form control
    • An aria-labelledby="id-of-the-text-that-works-as-a-label" on the form control
  • There should be a heading.
  • Every group of related controls should have a <fieldset> with a <legend>.
  • Every appropriate field should have an autocomplete attribute with the appropriate value. See the WCAG Input Purposes for User Interface Components list.
  • Use HTML5 input types to aid user input.

Errors

Here is some guidance on how to handle error messages in your code:

  • Where possible, have error messages as children elements of label elements.
  • Where not possible, use aria-describedby="id-of-the-error-message-element" on the form control.
  • Add aria-invalid to invalid fields using client-side scripting.
  • Add aria-required to mark required fields as well as the HTML5 required attribute.

CSS / presentation

Here are some tips for using CSS in an accessible way:

  • Only use CSS for layout and formatting, not whitespace in the HTML.
  • Make all click or touch targets at least 44 x 44 pixels (except when inline in a block of text).
  • Set a background-color whenever a color is set.
  • Use responsive web design.
  • Don’t set fixed width containers (that would clip, truncate, or obscure text, or cause horizontal scrolling, when text is enlarged).
  • Don’t use viewport-based units for font size.
  • Try to avoid flashing content (AAA). Any areas with flashing should be small (about thumbnail size), and flash fewer than three times a second (AA).

JS / behaviour

Here are some tips for using JS in an accessible way:

  • Every component should be keyboard accessible, and users should be able to tab in to and out of it (AA allows for some exceptions).
    • The tab order should also be logical. Don’t add tabindex if you can avoid it.
  • Use input-agnostic event handlers (focus, blur, click), not touch-only.
  • Only perform actions at the end of an event, not on down events. Alternatively, provide an undo option.
  • Try to avoid hiding content behind a hover. If you do, make sure:
    • The revealed content can be hovered on too
    • It can be dismissed by moving the pointer or changing focus (e.g. using Esc)
    • It stays until it’s dismissed
  • Any audio or video should have pause / stop controls, and should not autoplay.
  • Don’t use automatic redirects like meta refresh and redirect.
  • Have pause, stop, and hide controls for any moving, blinking, or scrolling content.
  • Any character key shortcuts should be able to be turned off, remapped, or are only active on focus.
  • Components should be accessible by default, by having a prop-based option for turning off the added accessibility features, in case the components are being combined in a different way.