Write Accessible Code

Key concepts

Know there are rules

WCAG 2.1 AA is the standard.

There are a lot of detailed success criteria. Come to our trainings, and ask for help.

Know how screen readers read

Screen reader users skim headings, regions or links to indicate items of interest, and they expect to hear their readers always announce the name, role and value of components: "Visited [value] link [role]: headings, regions or links [name]."

Don't reinvent the wheel

Whenever possible, use semantic elements that assistive devices already understand or accessible frameworks like Bootstrap. If you must do something that cannot be accomplished with a semantic element, follow established design patterns.

Test as you work

DIY testing with an automated testing tool, a keyboard and a screen reader will naturally guide you into discovering and fixing most common issues.

The earlier you find them, the easier to fix.


Accessibility requirements relevant to front-end development cluster around issues of screen reader hinting, keyboard and mobile compatibility, and interactive and form controls.


Structuring the page

  1. Use semantic elements rather than CSS for lists, tables, headings, etc. They are understood and announced by screen readers, and many have special browsing modes (jump to next heading/table cell/list item). Simple containers elements (<div>, <span>) do not provide any of this.
  2. Help users skim:
    • Use headings to label content groupings.
    • Provide HTML landmark regions (header, nav, main, footer). If you have more than one of the same type (site nav vs section nav), differentiate identical landmarks like <nav> elements with headings or aria-labels: (the main "site" navigation, the vertical "section" navigation).
    • Use clear and specific page titles. In a single page app, each navigation event should change the title; this sets the window title for screen readers.
    • Make sure every focusable element contains accessible text or an aria label, especially icon buttons.
  3. Provide a "skip to main" link, and point it at a named anchor. Targeting unfocusable elements may scroll them into view, but only focusable targets actually transfer focus for keyboard users.

Responsive breakpoints & reflow

  1. Users with low vision are likely to have overridden the default font or font size, which can cause unexpected overflows of fixed-size containers. Size elements relative to the font size (em or rem) instead of using px units, and refrain from limiting the height of text containers.
  2. Make sure your code supports accessible design considerations for device flexibility regarding element scaling for small, large, low PPI and high PPI devices. Expect users to commonly have viewports as narrow as 320px and as wide as 2560px.
  3. Do not assume users viewing your narrowest (mobile) breakpoint will not be using a keyboard and/or mouse. Their phone may be paired with an alternate input device, or they may have enlarged the text size enough to display the mobile breakpoint on a desktop computer. Your mobile theme still needs :focus indicators and keyboard click handlers.


  1. Make sure every form element has an accessible label for screen readers, and can be operated by a keyboard.
  2. Identify errors specifically, and be sure to tag errors well for screen readers. Be as helpful as possible; e.g.,  "Provide date as YYYY-MM-DD," rather than "Invalid date format."
  3. If your form handles legal commitments or financial transactions, be sure to read the detailed guidelines regarding user review, correction and confirmation.

Consult the W3C Forms Tutorial for more depth and code samples.

Custom interactive components

Modals, Slideshows, Menu Bars, Accordions, Tab Panels...every part of an interactive component, from its outer container to its buttons, status messages and focus transfers, needs to be accessible to keyboards and screen readers. 

Whenever possible, use pre-tested accessible frameworks or design patterns. Shoehorning accessibility into custom elements is error-prone and time consuming. The native, semantic <button> does everything <div aria-role="button" aria-label="fake button" tabindex="0" onclick="clickHandler"> does, without needing additional JavaScript to handle Enter and Space keypresses.

Before creating your own interactive components, read the MDN WAI-ARIA basics.

Some general tips:

  1. Every focusable element (links, buttons, fields and anything with a tabindex value of 0 or greater) must have a clear, visible visual indication of :focus.
    1. Keyboard users use the Tab key (or the equivalent) to advance through links and interactive elements. They need :focus states on elements to see where their cursor is, keyboard click event handlers for custom buttons, and skip links to get past navigation.
    2. Screen readers often do not trigger hover OR focus events. Screen readers in their default reading mode explore pages using a virtual cursor, and do not advance the "real" cursor to trigger a :focus event unless the user clicks to interact. This is incompatible with hover-based design patterns.
  2. Remember to communicate name, role and value/state. E.g., for an accordion toggle:
    1. Name: I'm an accordion!
    2. Role: Button
    3. State: Collapsed
      That would come from <button aria-expanded="false>I'm an accordion</button>
  3. Manage keyboard focus in and out of modal elements:
    • Set a short JavaScript timeout and then transfer keyboard focus to the first focusable element in a modal or overlay.
    • When the modal closes, transfer focus back to the button that launched it.
  4. Make sure users can pause, stop or hide anything that moves or refreshes automatically.

Hiding elements is surprisingly complicated

Before hiding an element, decide whether it should be hidden from all users or only certain users. The following design patterns work well in the right situation:

  1. To hide an element visually and hide the element's independent touch target: use the CSS clip pattern.
    1. This pattern reduces the element to a transparent pixel. Unlike the previous pattern, a mobile user is unlikely to find this hidden element out of context when running their finger over the page, but it will still be read in the context of its container. This is ideal for elements which explain their containers or context: hidden text alternatives inside icon buttons, abbreviation expansions, text alternatives for background images, etc.
  2. To hide an element visually but still let screen readers easily find and read it: use absolute positioning and opacity:0.
    1. The element will be invisible, but still has a touch target, so if a mobile screen reader user is running their finger around the page, it will be announced in that location. Just be sure to position it in an empty place on the page, so it can be independently touched. Useful for things like invisible section headers and skip-to-main links.
  3. To prevent the keyboard from focusing a visible and focusable element (e.g., a redundant link): add an attribute of tabindex="-1". The cursor will jump right past it.
    • Note that a mouse user clicking on a component with a negative tabindex will have their focus redirected to the previous element, so you should reserve this attribute for small components (buttons, links) rather than large containers (<main>), or mouse users will be startled to find their browser scrolling up to the top of the page when they click.
  4. To prevent screen readers from reading a visible but non-focusable element (and all of its children): add an attribute of aria-hidden="true." 
    • Used to conceal irrelevant elements from screen readers e.g., the icon next to the accessible text in a button: <button><img aria-hidden="true" />Menu</button>.
    • This attribute should never be given to a focusable element or an element with focusable children, as the screen reader user will still be able to Tab to the element, but their screen reader will abruptly stop speaking.
  5. To prevent screen readers from focusing and reading a visible and focusable element: use both tabindex="-1" and aria-hidden="true".
    • Generally used to hide redundant links ("read more") or offscreen content.
  6. To hide an element from everybody: set the element to display:none; or give it an HTML hidden attribute.
    • Used for elements that should be completely unreachable: they not only become invisible, they are removed from the element lists for keyboard focus and screen reader landmarks. Generally used for things like inactive tabs.

A note on temporarily-hidden content (hidden on scroll, lazy-loaded, etc): display:none and aria-hidden also remove elements from the page's list of elements a screen reader user can jump to. Since users often start with those lists to build their mental mode of the page and are not expecting the lists to change, if you want to temporarily hide content or page regions, hide the content, not the region, so that screen readers can still detect the landmark and header, and know that the hidden content exists. E.g., if you have a menu toggle button, place the toggle button inside the <nav> container and only toggle the menu items, rather than placing it above the container and toggling the whole container. This way the <nav> and <h2> are always available, and they tell the screen reader user where to go to reveal the hidden content:
    <h2 class="sr-only">Site Navigation</h2>
    <ul style="display:none;">...</ul>

Hiding Patterns Compared

MethodVisual StateKeyboard StateDesktop Screen ReaderMobile Screen Reader
CSS clipHiddenFocusableReadable + FocusableReadable, Nearly unfocusable (caution)
opacity:0HiddenFocusableReadable + FocusableReadable + Focusable
tabindex=-1VisibleUnfocusableReadable, UnfocusableReadable, Unfocusable
aria-hiddenVisibleFocusableUnreadable but possibly focusable (caution)Unreadable but possibly focusable (caution)
tabindex + aria-hiddenVisibleUnfocusableHiddenHidden


Further Reading and Online Classes