Skip to main content

Accessibility

Semantic HTML

Structure Accessibility

  • Semantics section reference.
  • <header>: role="banner"
  • <nav>: role="navigation"
  • <main>: role="main"
  • <aside>: role="complementary"
  • <section>: role="region"
  • <article>: role="article"
  • <footer>: role="contentinfo"
<header>
<nav>
<ul>
<li><a></a></li>
</ul>
</nav>
</header>

<main>
<section></section>
</main>

<footer></footer>

Heading Accessibility

  • 7 heading levels: <div role="heading" aria-level="7"></div>
  • One <h1> per page
  • Have a HTML sitemap.
  • Support keyboard navigation (Key and Tab Index).
  • Breadcrumbs a11y:
    • aria-label="breadcrumbs"
    • aria-label="page"
<nav aria-label="breadcrumbs">
<ol>
<li>
<a href="https://example.com/"> Home </a>
</li>
<li>
<a href="https://example.com/products"> Products </a>
</li>
<li>
<a href="https://example.com/products/childrens-clothing"> Children's clothing </a>
</li>
<li>
<a href="https://example.com/products/childrens-clothing/shoes" aria-current="page"> Shoes </a>
</li>
</ol>
</nav>

Section Accessibility

<section aria-labelledby="sectionHeader1">
<h2 id="sectionHeader1">A great section</h2>
</section>
<section aria-labelledby="sectionHeader2">
<h2 id="sectionHeader2">An even better section</h2>
</section>

Article Accessibility

The <article> element is used to represent a fully self-contained region of content

<article>
<header>
<h1>Why you should buy more cheeses than you currently do</h1>
</header>
<section>
<header>
<h2>Part 1: Variety is spicy</h2>
</header>
<!-- cheesy content -->
</section>
<section>
<header>
<h2>Part 2: Cows are great</h2>
</header>
<!-- more cheesy content -->
</section>
</article>

Reference Accessibility

  • <cite>
  • <q>
  • <blockquote>
  • <code>
<p>
Every time Kenny is killed, Stan will announce
<q cite="http://en.wikipedia.org/wiki/Kenny_McCormick#Cultural_impact"> Oh my God, you/they killed Kenny! </q>.
</p>
<blockquote cite="https://www.huxley.net/bnw/four.html">
<p>Words can be like X-rays, if you use them properly – they'll go through anything. You read and you're pierced.</p>
</blockquote>

<cite>– Ados Huxley, Brave New World</cite>
<article>
<h2 id="article1-title">My article</h2>
<p>Article brief description with truncation...</p>
<a href="article1-url" aria-labelledby="article1-title">Read more</a>
</article>

Text Accessibility

  • <b>
  • <strong>
  • <mark>
  • <ins>
  • <del>
  • <abbr>: 专有名词解释 <abbr title="HyperText Markup Language">HTML</abbr>

不要将 <b> 元素与 <strong><em><mark> 元素混淆:

  • <strong> 元素表示某些重要性的文本
  • <em> 强调文本
  • <mark> 元素表示某些相关性的文本

Text Color A11Y

  • Devtool inspect elements A11Y for color contrast ratio.
  • Don't forget ::selection.

Text Spacing A11Y

  • line-height of blocks of text should be 1.5.
  • space between paragraphs should be 1.5 times the line-height (so a minimum of 2.25 rem).
  • Line height (line spacing) to at least 1.5 times the font size.
  • Spacing following paragraphs to at least 2 times the font size.
  • Letter spacing (tracking) to at least 0.12 times the font size.
  • Word spacing to at least 0.16 times the font size.

Button Accessibility

Use <button> for clickable elements

Image Accessibility

  • alt=""

SVG Accessibility

  • <title>
  • <desc>
<svg width="100" height="75">
<title>Dark rectangle</title>
<desc>A grey rectangle with rounded corners and a dark green border</desc>
<rect width="75" height="50" rx="20" ry="20" fill="#666" stroke="#229b23" stroke-fill="1" />
</svg>

Figure Accessibility

<figure aria-labelledby="image-alt">
<img src="" alt="" />
<br />
<figcaption id="image-alt"></figcaption>
</figure>

Audio Source Accessibility

  • src=""
  • type=""

Form Accessibility

With fieldset and legend:

<form role="form">
<fieldset>
<legend>Choose one of these three items:</legend>
<input id="one" type="radio" name="items" value="one" />
<label for="one">Choice One</label><br />
<input id="two" type="radio" name="items" value="two" />
<label for="two">Choice Two</label><br />
<input id="three" type="radio" name="items" value="three" />
<label for="three">Choice Three</label>
</fieldset>
</form>

Input Accessibility

  • label[for] input.
  • aria-label and aria-describedby for input hint.
  • aria-invalid for error input.
  • aria-hidden for hidden input.
<form role="form">
<label for="name">Name:</label>
<input id="name" name="name" type="text" />
</form>
<form role="form">
<label for="name">Name:</label>
<span class="prefix-input">
<span class="prefix-icon" id="name-icon" aria-label="Input Prefix Icon">
<icon />
</span>
<input id="name" name="name" type="text" aria-describedby="name-icon" />
</span>
</form>
<form role="form">
<label for="email-address"> Your Email Address </label>
<span id="email-error"> Error: Your email address must contain an @ symbol </span>
<input id="email-address" name="email-address" type="email" aria-describedby="email-error" aria-invalid="true" />
</form>
export default function Field() {
return (
<>
<div className="user-code-field">
<input
id="userCode"
aria-describedby={
errors.userCode ? 'user-code-error' : 'user-code-help'
}
/>
<span id="user-code-help" className="user-code-help">
Enter your 4 digit user code
</span>
</div>
{errors.userCode && (
<div id="user-code-error" role="alert" className="error">
You must enter your 4 character user code
</div>
)}
</>
)
}

Time Accessibility

<time datetime="2016-09-15">Thursday, September 15<sup>th</sup></time>

Address Accessibility

<footer>
<section class="contact" vocab="http://schema.org/" typeof="LocalBusiness">
<h2>Contact us!</h2>
<address property="email">
<a href="mailto:us@example.com">us@example.com</a>
</address>
<address property="address" typeof="PostalAddress">
<p property="streetAddress">123 Main St., Suite 404</p>
<p>
<span property="addressLocality">Your Town</span>, <span property="addressRegion">AK</span>,
<span property="postalCode">12345</span>
</p>
<p property="addressCountry">United States of America</p>
</address>
</section>
</footer>

Color Contrast

  • more than 4.5:1 ratio

Keys and Tabindex Accessibility

<a id="second" href="" accesskey="c"></a>
document.addEventListener('keyup', (event) => {
switch (event.keyCode) {
// escape
case 27:
// exit
break
// enter || space bar
case 13 || 32:
// submit or something
break
// left arrow
case 37:
// move back / previous
break
// right arrow
case 39:
// move forward
break
// up arrow
case 38:
// move up
break
// down arrow
case 40:
// move down
break
default:
throw new Error('Unsupported key!')
}
})
/**
* Traps the tab key inside of the context, so the user can't accidentally get
* stuck behind it.
*
* Note that this does not work for VoiceOver users who are navigating with
* the VoiceOver commands, only for default tab actions. We would need to
* implement something like the inert attribute for that (see https://github.com/WICG/inert)
* @param {object} e the Event object
*/
export function trapTabKey(e, context) {
if (e.key !== 'Tab')
return

const focusableItems = getFocusable(context)
const focusedItem = document.activeElement

const focusedItemIndex = focusableItems.indexOf(focusedItem)

if (e.shiftKey) {
if (focusedItemIndex === 0) {
focusableItems[focusableItems.length - 1].focus()
e.preventDefault()
}
} else {
if (focusedItemIndex === focusableItems.length - 1) {
focusableItems[0].focus()
e.preventDefault()
}
}
}

Self-Closing Tags

Self-closing tags (<tag />) do not exist in HTML. If a trailing / (slash) character is present in the start tag of an HTML element, HTML parsers ignore that slash character:

<div>This text is inside the div.</div>
<div />This text is inside the div.

<input />This text is outside the input.
<input>This text is outside the input.</input>

Further reading:

  • Void elements in HTML.
  • Self-closing tags in JSX.
  • Self-closing tags in Svelte.

ARIA

Web Accessibility Initiative - Accessible Rich Internet Applications:

  • aria-label.
  • aria-labelledby="dropdownMenuButton": dropdown/form>.
  • aria-describedBy: input + small.
<label id="l1" for="f3">label text</label>
<input type="text" id="f3" aria-labelledby="l1 l2" />
<p>other content</p>
<span tabindex="-1" id="l2">more label text</span>

<div aria-describedby="test">text</div>
<div id="test" role="tooltip">tooltip text</div>

<div role="dialog" aria-label="login" aria-describedby="log1">
<div id="log1" tabindex="-1">Provide user name and password to login.</div>
</div>
  • aria-disabled="true": disable element.
  • aria-hidden="true".
  • aria-controls="navbarSupportedContent": navigation/select.
  • aria-expanded="false": dropdown.
  • aria-haspopup="true": dropdown/popup.
  • aria-current="pages: breadcrumb.
  • aria-valuenow/aria-valuemin/aria-valuemax: progress.
  • role.
    • <header>: role="banner".
    • <nav>: role="navigation".
    • <main>: role="main".
    • <section>: role="region".
    • <article>: role="article".
    • <aside>: role="complementary".
    • <footer>: role="contentinfo".
    • <form>: role="form".
    • 7th heading level: <div role="heading" aria-level="7"></div>.
    • role="button".
    • role="checkbox".
    • role="gridcell".
    • role="link".
    • role="menuitem".
    • role="menuitemcheckbox".
    • role="menuitemradio".
    • role="option".
    • role="progressbar".
    • role="radio".
    • role="scrollbar".
    • role="searchbox".
    • role="separator (when focusable)".
    • role="slider".
    • role="spinbutton".
    • role="switch".
    • role="tab".
    • role="tabpanel".
    • role="textbox".
    • role="tooltip".
    • role="treeitem".
    • role="presentation": removes the semantics of an element. If set an interactive or focusable element to role="presentation", assistive technology user will not know what it is or how to use it.
    • role="application".
<button class="list-expander" aria-expanded="false" aria-controls="expandable-list-1">Expand List</button>
<ul id="expandable-list-1">
<li><a href="http://example.com">Sample Link</a></li>
<li><a href="http://example.com">Sample Link 2</a></li>
<li><a href="http://example.com">Sample Link 3</a></li>
</ul>
const listExpander = document.querySelector('.list-expander')
const list = document.querySelector('#expandable-list-1')

listExpander.addEventListener('click', (e) => {
if (list.getAttribute('aria-expanded') === 'true')
list.setAttribute('aria-expanded', 'false')
else
list.setAttribute('aria-expanded', 'true')
})

Dialog ARIA Role

<div id="dialog_layer" class="dialogs">
<div
id="dialog1"
role="dialog"
aria-labelledby="dialog1_label"
aria-describedby="dialog1_desc"
aria-modal="true"
class="hidden"
>
<h2 id="dialog1_label" class="dialog_label">Address Added</h2>
<p id="dialog1_desc" class="dialog_desc">
The address you provided has been added to your list of delivery addresses. It is ready for immediate use. If you
wish to remove it, you can do so from
<a href="#" onclick="openDialog('dialog2', this)"> your profile. </a>
</p>
<div class="dialog_form_actions">
<button type="button" id="dialog1_close_btn" onclick="closeDialog(this)">OK</button>
</div>
</div>
<div
id="dialog2"
role="dialog"
aria-labelledby="dialog2_label"
aria-describedby="dialog2_desc"
aria-modal="true"
class="hidden"
>
<h2 id="dialog2_label" class="dialog_label">End of the Road!</h2>
<p id="dialog2_desc" class="dialog_desc">
You activated a fake link or button that goes nowhere! The link or button is present for demonstration purposes
only.
</p>
<div class="dialog_form_actions">
<button type="button" id="dialog2_close_btn" onclick="closeDialog(this)">Close</button>
</div>
</div>
</div>

HTML First over ARIA

<!--div role="banner"-->
<header></header>

<!--div role="navigation"-->
<nav></nav>

<!--div role="main"-->
<main></main>

<!--div role="region"-->
<section [accessible name]></section>

<!--div role="complementary"-->
<aside></aside>

<!--div role="contentinfo"-->
<footer></footer>

<!--div role="form"-->
<form></form>

<div role="search"></div>

Accessibility Best Practices

A11y audit list:

  • Keyboard-only navigation.
  • Voice control.
  • Screen reader.
  • High contrast mode.
  • Dark mode.
  • Browser zoom.
  • Don't use aria-hidden on the <body> element.
  • Complete meta header:
    • Add missing languages.
    • Make sure document has a title element.
    • Tool: react-helmet.
  • Fix low text contrast: 确保文本与其背景保持足够的对比.
  • 不要将颜色作为传达信息的唯一手段 (色盲/弱).
  • Add missing alternative text.
  • Remove empty links and buttons.
  • 注意表单:
    • Add missing labels.
    • 提供输入焦点的视觉提示.
  • 避免组件识别障碍.
  • Make sure IDs and Keys of elements are unique.
  • Required context role.
  • Required aria attribute.
  • Valid aria attribute.

Accessibility Checklist

Accessibility Tools

Accessibility References