Let's make an accessible component

  1. git clone
  2. cd accessibility-demo
  3. npm install

This is how you're doing it wrong.


Tabbed interface

  1. Visual presentation
  2. Visual behavior
  3. Keyboard behavior

Tabs are not a native web paradigm. They come from desktop interfaces.


  • Retain the expected visual design
  • Operable via non-pointer/touch input
  • Operable and understandable by screen reader

The Plan

  1. Review existing code
  2. Implement basic keyboard support
  3. Implement expected tab interface behaviors
  4. Implement easy HTML wins
  5. Improve screen reader support

Starter HTML (JSX)

<div className="tablist">

  <div className="tab">Tab name</div>



  • Interactive elements should be keyboard focusable
  • How can we make the "tab" keyboard focusable?

That was easy

<div className="tablist">

  <div className="tab">Tab name</div>

  <button className="tab">Tab name</button>



 git checkout 02-basic-keyboard-focus​

Don’t forget focus states

  • Users who rely on non pointer/touch input (e.g. keyboard) rely on focus states to know where they are in the interface.
  • Like many AX considerations, these affordances often benefit the non-disabled


a:focus {

  /* Don't do this without adding back a focus state! */

  outline: none;


How should a tabbed interface work?


  • Left and Right arrow keys move between tabs
  • Tab key and down arrow key should move focus from selected tab to tab panel (content)
  • Shift + Tab move focus from tab panel back to selected tab

problems to solve

  • Take non-selected tabs out of the normal tab flow
  • Make the tab panel focusable
  • Pressing the left or right arrow key will select the adjacent tab and set focus on it

House keeping: <TabManager />

  • Move selected state management up from <Tabs /> to <TabManager />
  • Maintain ref for selected tab
  • Maintain ref for tab panel
  • onKeyDown methods for left, right, and down arrow keys
  • Expose data (state), refs, and keyboard event handlers via render props
 git checkout 03-proper-tab-keyboard-controls

Tab focus and behavior

{diamonddogs.map(member => (
  tabIndex={tabState.selected !== member.id ? -1 : undefined}
  ref={tabState.selected === member.id && tabState.selectedTabRef}
  onClick={() => {
  className={classNames(['tab', {'tab--selected': tabState.selected === member.id }])}

Manage Tab Panel Focus

<section ref={tabState.panelRef} tabIndex={0}>

Keyboard Events

handleKeyDown = event => {
 switch (event.which) {
  case KEYS.right: {
    selected: findNext(
   }, () => {
  case KEYS.left: {
   // ...

Keyboard Events

handleKeyDown = event => {
 switch (event.which) {
   // ...
   case KEYS.down: {
   // ...

What about screen readers?

MacOS voiceover

  • Cmd + Fn + F5: Turn on/off VO
  • Ctrl + Option + A: Start reading
  • Ctrl: Stop reading
  • Ctrl + Option + Right: Read next
  • Ctrl + Option + Right: Read previous


Back to Basic HTML

  1. <title>Diamond Dog Team Page</title>

  2. <h1 className="page-title">Diamond Dogs</h1>

  3. <img





 git checkout 04-additional-html-semantics

marking up the bio

<dl className="bio">
 <dt className="bio__label">Hometown</dt>
 <dd className="bio__item">{selectedMember.hometown}</dd>
 <dt className="bio__label">Time at Vistaprint</dt>
 <dd className="bio__item">{selectedMember.employment}</dd>
 <dt style={{ display: "none" }}>Biography</dt>
 <dd className="bio__item">{selectedMember.bio}</dd>

Visually hidden / Announced by Screen readers

<dt style={{ display: "none" }}>Biography</dt>

display: none; is not announced by screen reader

Visually hidden / Announced by Screen readers

<dt className="visually-hidden">Biography</dt>
.visually-hidden {
 border: 0;
 clip: rect(0 0 0 0);
 height: 1px;
 margin: -1px;
 overflow: hidden;
 padding: 0;
 position: absolute;
 width: 1px;
 white-space: nowrap;

Still not enough

HTML lacks all the necessary elements to accurately describe our app to assistive technology


Accessible Rich Internet Applications: supplements HTML with additional roles, attributes, and states to describe UI widgets


Tab List

<div role="tablist" className="tablist" aria-label="Team members">
 git checkout 05-aria


 aria-selected={tabState.selected === member.id}
 aria-controls={tabState.selected === member.id
  ? 'tab-panel'
  : undefined

Tab Panel


Accessibility helps everyone

There’s really no right or wrong in inclusive design. It’s just about trying your hardest to provide a valuable experience to as many people as you can... no matter how they are operating or reading your interface...

Did we do it?

Other considerations:

  • Cognitive disabilities
  • Visual impairments (e.g. low vision, color blindness)


Let's make an accessible component

By Eric Masiello

Let's make an accessible component

  • 488