Nino's Blog

A11y checklist


In the previous blog post, I wrote how should look basic semantic markup, which will help a lot with a11y. But to have 💯 percent accessible website for users with impairments, we need to do more. Here is a checklist what we need to do:


  • Use heading elements to introduce content.
  • Only one h1 per page/view.
  • Order h elements from smaller to bigger and don’t skip them.


  • Don’t use complicated layouts, because they can be confusing to understand and use.
  • Make sure that button, a, and label element content is unique and descriptive. “Terms like “click here” and “read more” do not provide any context. Some people navigate using a list of all buttons or links on a page or view. When using this mode, the terms indicate what will happen if navigated to or activated.”
  • Use good color contrast (text, icons, input elements border, text over images and videos, etc.)
  • Use list elements (ol, ul, and dl) for list content.
  • Use the table element to describe tabular data. Don’t forget about th and caption within table.
  • Prefer left-aligned text, because it’s more readable.
  • Use role to change the semantics of an element so it will be announced properly by a screen reader.
  • Add aria-live to changing elements, like error messages on input fields
  • Add aria-expanded attribute to provide information about whether an element is in an expanded or collapsed state, most often is used for dropdowns.


  • Use alt attribute (empty for decorative images).
  • For images containing text, make sure the alt description includes the image’s text.
  • Provide a text alternative for complex images such as charts, graphs, and maps.
  • Make sure that svg elements include the code focusable=“false” when they are the child element of a focusable element.
  • Add aria-hidden=“true” to svg that is decorative.
  • Ensure that img elements with an svg source includes the role=“img” attribute.


  • Add label element to all input elements (for-id pair).
  • Use fieldset and legend elements and autocomplete attribute where appropriate.
  • Display input errors in the list above the form after submission.
  • Add aria-describedby on inputs to connect them to to error messages.
  • Don’t use only colors for error, warning, and success states (think of color blind people).
  • If necessary use aria-invalid on input fields
  • If you have trouble styling input elements check


  • Make your content legible in specialized browsers modes, such as Windows High Contrast or inverted colors. Are your icons, borders, links, form fields, and other content still present?
  • Content should be readable when font size is set to 200%.
  • Keep enough space between content for people who use zoom software.
  • Make sure color isn’t the only way information is conveyed (test site in grayscale).


  • Use subtile animation without lots of flashes.
  • Enable pausing background video.
  • Make use of prefers-reduced-motion media query


  • Make sure that the media does not use autoplay.
  • Use appropriate markup for media controls (e.g. <input type="range"> for volume slider, mute button has pressed toggle state when active)
  • All media should be pausable. “Provide a global pause function on any media element. If the device has a keyboard, ensure that pressing the Space key can pause playback. Make sure you also don’t interfere with the Space key’s ability to scroll the page/view when not focusing on a form control.”
  • For video add caption capability and remove strobing or flashing animations that can cause trigger seizures.
  • For audio add a transcript.


  • If it is a link (navigates to a new page), use a element or wrap content with it.
  • If it is button (performs an action on a page), use button, not a div. Button should be focusable by keyboard, support being disabled, support ENTER and SPACE keys to perform action and be announced properly by a screen reader. You get all that for free when using a button.
  • Make links recognizable. Color alone is not sufficient to indicate presence of link. Underline is commonly-understood communication that element is a link.
  • Avoid outline: none. When to set focus?
input[type="text"]:focus {
  outline: 1px solid orange;

A good rule of thumb is to ask yourself, “If you clicked on this control while using a mobile device, would you expect it to display a keyboard?”

If the answer is “yes,” then the control should probably always show a focus indicator, regardless of the input device used to focus it. A good example is the <input type="text"> element. The user will need to send input to the element via the keyboard regardless of how the input element originally received focus, so it’s helpful always to display a focus indicator.

If the answer is “no”, then the control may choose to show a focus indicator selectively. A good example is the button element. If a user clicks on it with a mouse or touch screen, the action is complete, and a focus indicator may not be necessary. However, if the user is navigating with a keyboard, it’s useful to show a focus indicator so the user can decide whether or not they want to click the control using the ENTER or SPACE keys.

To do this checkout :focus-visible and :focus-within pseudo-classes (don’t forget to checkout browers support and use polyfills if need it).

/* This will hide the focus indicator if the element receives focus via the
mouse, but it will still show up on keyboard focus.   */
.js-focus-visible :focus:not(.focus-visible) {
  outline: none;
  • Hide interactive element that could receive focus, like offscreen side navigation, with display: none and visibility: hidden.
  • Provide a skip link and make sure that it is visible when focused. “Many sites contain repetitive navigation in their headers, which can be annoying to navigate with assistive technology. Use a skip link to let users bypass this content.

A skip link is an offscreen anchor that is always the first focusable item in the DOM. Typically, it contains an in-page link to the main content of the page. Because it is the first element in the DOM, it only takes a single action from assistive technology to focus it and bypass repetitive navigation.”

<a class="skip-link" href="#main">Skip to main</a><main id="main">
  [Main content]
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: #000000;
  color: white;
  padding: 8px;
  z-index: 100;

.skip-link:focus {
  top: 0;
  • Identify links that will be opened in a new window, e.g.
<a href="knitting.html" target="_blank"
  >All about Knitting (opens in new window)</a


“It is important that your interface and content can be operated, and navigated by use of a keyboard. Some people cannot use a mouse, or may be using other assistive technologies that may not allow for “hovering” or precise clicking.”

  • All interactive HTML elements, like a, button, input (with many forms), select, textarea should be focusable with TAB and SHIFT + TAB. Their values should be manipulated with ARROW KEYS and keys like ENTER and SPACE.
  • Have focus style for interactive elements.
  • Keyboard focus order matches the visual layout.
  • Remove invisible focusable elements, like inactive dropdowns, offscreen navigations and modals.
  • For other keyboards best practices checkout wai-aria-practices-1.1 guide.


  • Ensure that viewport zoom is not disabled.
  • Ensure linear content flow. Remove tabindex attribute values that aren’t either 0 or -1 (tabindex > 1 is anti-pattern). Elements that are inherently focusable, such as links or buttons, do not require a tabindex. Elements that are not inherently focusable should not have a tabindex applied to them outside of very specific use cases. Basically, you should rarely use tabindex, if you use native html elements that are tabable by default (button, link, inputs…).
  • If you’re building a complex component use rowing tabindexes. You may need to add additional keyboard support beyond the focus. Consider the native select element. It is focusable, and you can use the arrow keys to expose additional functionality (the selectable options).

To implement similar functionality in your components, use a technique known as “roving tabindex”. Roving tabindex works by setting tabindex to -1 for all children except the currently active one. The component then uses a keyboard event listener to determine which key the user has pressed.

When this happens, the component sets the previously focused child’s tabindex to -1, sets the to-be-focused child’s tabindex to 0, and calls the focus() method on it.

  • When we change to a new page, we need to change the focus to the new component that was just mounted.
  • Avoid using autofocus because people may be disoriented when the focus is moved without their permission.
  • Remove title attribute tooltips. The title attribute has numerous issues, and should not be used except for iframes. Before:
<div role="toolbar">
  <button tabindex="-1">Undo</div>
  <button tabindex="0">Redo</div>
  <button tabindex="-1">Cut</div>


<div role="toolbar">
  <button tabindex="-1">Undo</div>
  <button tabindex="-1">Redo</div> // Becomes -1
  <button tabindex="0">Cut</div> // Becomes 0


  • Adjust website to landscape mode.
  • Remove horizontal scrolling.
  • Button and links can be activated with ease. Button height should be at least 40px, enough spacing between them…



Written by Nino Majder who lives and breaths web development. Follow him on Twitter