Web Accessibility Guidelines


Why guidelines?

  • To make your applications accessible to people with disabilities and also for those without
  • To protect your company from discrimination lawsuits
  • To improve Search Engine Optimization
  • To provide a better overall usability

There are two main web conformance guidelines: Section 508 and WCAG 2.1.

Section 508

A US policy that targets websites developed or used by the federal US government. More info under www.section508.gov.


A world wide accepted standard of web accessibility. Web Conformance Accessibility Guidelines (WCAG) version 2.1 was published in 2018 by the WCAG Working Group which is part of the World Wide Web Consortium (W3C) Web Accessibility Initiative (WAI). More information is available on the offical W3C website.

POUR Principles

WCAG 2.1 contains 78 total criteria organized as 13 guidelines under 4 principles: Perceivable, Operable, Understandable, Robust (POUR).

  • Perceivable: User must be able to perceive the information being presented. That means all of the content has to be made available to everyone, including images with description, videos with closed captions and so forth.
  • Operable: Users must be able to operate the interface. That means all your content must be accessible via keyboard, mouse, touch, speaking devices or any other method of interaction.
  • Understandable: Users must be able to understand the information as well as the operation of the user interface.
  • Robust: Users must be able to access the content as technologies advance.

Levels of conformance

Each guideline has 3 levels of conformance: Level A, Level AA and Level AAA. Each higher level includes conformance of lower levels (AAA contains AA which contains A).

Level A: Most basic of web accessibility features. Every website should have it. Level AA: Contains the biggest and most common barriers. Level AAA is the highest level.

Which guideline to choose?

Aim for WCAG 2.1 Level AA because it got its latest update in 2018 and is a global standard. Also Level AA covers all of Section 508.


Using the most suitable HTML elements and placing them at the right position already contributes a lot to improve the accessibility. The HTML structure can be considered the machine code that is used as input for screen readers. Landmarks is a term used for HTML elements like <header>, <nav>, <main> (once per page), <footer> and <aside>.

Content structure is the way information is structured on your web site in a way that it makes sense. You can think of that as the human readable code, which includes lists, headings, paragraphs and more.

  • Define a proper doctype
  • Make sure element ids are unique
  • Properly open and close element tags
  • Check that there are no duplicate element attributes
  • Designate the language in the html tag
  • Wrap language content that is different from the default defined one and add a corresponding lang attribute
  • Add these meta tags
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie-edge" />
<meta name="viewport" content="width-device-width, inital-scale=1.0, shrink-to-fit=no" />
  • Choose a descriptive title
  • Use relative CSS units: rem or em

If you have many navigations on your page, for example a site-wide navigation and a page-navigation, then you should add aria-label="site" and aria-label="page" to the nav elements.

  • Restructure your page using landmark elements (see above)
  • Restructure you page to use proper list elements such as <ul>, <ol> and <dl>
  • Text of buttons that do the same, should have the same text, e.g. "Search" instead of having one button labeled "Search" and the other "Find".
  • Add a sitemap
  • Use proper hierarchical headings
  • Link text should not be "here" or "read more", instead the purpose of the link should make sense like "read more about our products"
  • Links that open up a new tab or refer to a file should have a note such as "(opens in new tab)" or say the file type.

Add a mechanism (e.g. a Skip Link) to bypass blocks of content that are repeated on multiple Web pages. A Skip Link is initially hidden and only visible when tab key on the keyboard is pressed. Then, upon activating the link, it jumps to the main section and hides itself again. Here is the implementation:

<a class="skip-link" href="#main">Skip to main</a>
.skip-link {
  left: -100%;
  position: absolute;

.skip-link:focus {
  left: 50%;
var skipLink = document.querySelector('.skip-link');
skipLink.addEventListener('click', function(e) {

Which links to

<main id="main" tabindex="-1">

Accessible tables

Do not use tables for layout purposes. A proper table HTML structure provides special keyboard navigation capabilities when using screenreaders. Use table <caption>, <thead>,<tfoot> and <tbody> to structure table data.

Add scope attributes to your tables such as <th scope="col">Product</th>. Other scopes are row, rowgroup and colgroup. You can add a visually hidden span containing a description of the table's content in the <caption>.

Keep your tables structurally as simple as possible. Avoid nesting too many rows and columns.

Accessible media

Accessible images

  • All images must have alt attribute, even those image having just decorative purpose using an empty alt="". Don't describe the image literally, but rather the purpose or meaning. Include any text that the image has.
  • For images that do not use <img> element, such as CSS background images, you should add a visually hidden text
  • Add role="img" to SVG images and use SVG's <title> and use <desc> for extended descriptions if necessary. Use aria-labelledby referencing the <title> and aria-describedby referencing <desc>

Audio and Video

Provide a transcript of the audio and captions for video. You can write it yourself, use a paid service or speech recognition software (Google docs Voice typing, Windows Speech Recognition, Apple Dictation, Dragon Naturally Speaking). Include names of speakers. Describe everything.

Transcription can be inline or linked to.

Don't add anything that flashes more than 3 times per second (it might cause seizures)

Responsive Web Design and Accessibility

Switching context

Prevent unexpected shift of focus. For example a navigation bar that is displayed as a selection list will load a page when using the keyboard to navigate down. That is an unexpected behavior. Instead add a Go button to activate the page change.

Keep the order of content/focus

Stack content and preserve its original order when shrinking the browser size. Visual order must match DOM order.

Mobile devices

  • Don't lock your design to a single display orientation like only portrait or only landscape
  • All functionality that uses gestures can be operated with a single pointer
  • Provide a way to cancel or undo actions. For example: Mouse down on a submit button then drag away and only now mouse up - that should not trigger a submit
  • be careful with off-screen content, because it is still accessible for screen readers. A proper way to hide content is to use display:none CSS rule or hidden attribute of an element. Use aria-hidden="true" to hide from screen readers only
  • Use relative units like em, rem and %, not pixels

Accessible forms

Use contrast with a ratio of 4.5 : 1 for text and images of text. Foreground color must stand out from background color. User interface components and graphical object must have a contrast ratio of at least 3:1.

  • Do not use input fields that only have placeholders to describe them, instead use visible labels that match the text that is presented visually
  • Use placeholders to hint if a certain input pattern should be applied, like a date or currency pattern
  • Group your form fields into logical groups using <fieldset> and <legend>
  • Mark required fields visually
  • Your forms must be operable via keyboard
  • When using keyboard-shortcuts users should have the ability to turn off, remap or activate shortcuts only on focus
  • Make keyboard navigation visible and highlight current focused form field
  • Don't rely solely on colors as indication for form (validation) status such as errors. Instead also provide the error text.
  • If input errors are detected then display a suggestion on how to fix them
  • Use input type where possible such as type="email" or "tel"
  • Use JavaScript to add aria-invalid="true" to fields containing errors
  • Add an errors section with aria-live="assertlive" (allows screenreaders to react to dynamically updated content) to the top of the form that contains all error messages
  • You can use tabindex attribute to make non-focusable elements focusable. Never set a tabindex greater than 0, because that makes tabbing behavior unreliable. Instead tabindex should follow the visual order and that in turn should follow the DOM order. Tabindex="-1" removes the element from the natural tab order, but allows it to stay focusable with JavaScript. Tabindex="0" adds an element to the natural tab order.

Surround inputs within a form tag. This allows users to submit a form using the enter key.

  <input ... />
  <input ... />
  <input ... />

Secondly the form tag enables screen readers to enter "forms mode". But keep in mind that forms mode disregards any extraneous content, that is content, that is not directly associated with a form. That also means: Text content which describes a form should either be directly associated with an input, or placed outside the form.

Associate labels and inputs either

<label>My Label
  <input type="text" />


<label for="my-label">My Label</label>
<input id="my-label" type="text" />

This also allows you to select an input by clicking on the label.

You can additionally (not exclusively) name inputs to help screen readers:

<input tpye="text" aria-label="My Label" />


<span id="my-label">My Label</span>
<input type="text" aria-labelledby="my-label" />

Additionally (not exclusively) use placeholders to give guidance on how to fill in the form input.

In long forms you can choose to group form fields into groups:

  <legend>My form section</legend>
    <label ... />
    <input ... />

    <label ... />
    <input ... />

But rather keep the legend short, because a screen reader reads the legend every time a label within a fieldset gets focused.

Forms validation

You might want to display error messages next to an invalid input field, but as mentioned above, a screen reader would ignore any element within a form that is not explicitly associated with a form element. But you can use aria-describedby with many ids:

<label for="password">Password</label>
<inpout id="password" type="text" aria-describedby="rule error" />
<span id="rule">Must be at least 10 characters long</span>
<div id="error">Password invalid</div>

Required fields should be spelled out "required" rather than using an asterisk. For screen readers you should add:

<label for="password">Password</label>
<input id="password" type="text" aria-required="true">

If a form element is invalid you can add aria-invalid="true" to it.

Dynamic content

Specify aria-live="assertive" on an element to alert the user when its content has been updated. "assertive" will interrupt the screen reader mid-sentence if the content changed. "polite" will wait until screen reader finished speaking. Add a live region to a div which contains form errors and the screen reader will read its contents out loud right after trying to submit the form.

This way a screen reader could read out loud any update of a progress bar for example. On a dashboard with many automatically updating elements you should give the user the opportunity to stop this behavior or at least allow to change the interval of reading out live updates.

Non-native interactions

Such as alerts, dropdown menus, accordions, modal dialogs. There are best practices.

Make content visually hidden

display: none to hide content does not do the trick. To make content still accessible to screen readers, do this:

Bootstrap UI Framework uses the class sr-only. You can also create this CSS class on your own:

.visually-hidden {
  position: absolute;
  clip: rect(0 0 0 0);
  height: 1px;
  width: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;

About Author

Mathias Bothe To my job profile

I am Mathias, born 40 years ago in Heidelberg, Germany. Today I am living in Munich and Stockholm. I am a passionate IT freelancer with more than 16 years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I am founder of bosy.com, creator of the security service platform BosyProtect© and initiator of several other software projects.

No comments yet.

Leave a comment