React
components
Accessibility-flavoredmake your design
system delicious
Kathleen McMahon@resource11

@resource11

https://noti.st/resource11
Slide deck posted after the talk

@resource11@resource11
Twitter | Instagram | GitHub

@resource11

Why accessibility first?

Good grief,
there’s anagenda...

Design systems are a cookbook
Design systems and React
Icons
Buttons
InputsDocumentation

@resource11

Who are you, again?

@resource11

@resource11

Cyclocross!

@resource11

@resource11

Dev Dinosaur

@resource11

@resource11

@resource11

@resource11

@resource11

@resource11

@resource11

HTML + CSS + JS

@resource11

JavaScript Frameworkof the weekDinosaurs are
ALWAYS the
hotness@resource11

@resource11

@resource11

Great design systems
combine team AND
consumer experience

@resource11@resource11

@resource11

First Priority

Business logic out,
accessibility in@resource11

Why accessibility first?

Our users are diverse
VisionSensory

Hearing

Language

Inclusive Design Persona Spectrum (CC BY-NC-SA 2.0)

Motor

Cognitive

Low bandwidth

@resource11

February 2019

WebAIM Million
An accessibility analysis of the top
1,000,000 home pages

@resource11

97.8%

of home pages had detectable
WCAG 2 failures.

@resource11

59%

of 3.4 million form inputs
were unlabeled

@resource11

60.1%
of the 1 million pages had
ARIA present

@resource11

Web Content
Accessibility Guidelines(WCAG)

@resource11

Imagine… accessibility
baked into components.

@resource11

Your Design
system is acookbook

@resource11

Cookbooks have
Personality

@resource11

@resource11

@resource11

@resource11

@resource11

@resource11

@resource11

@resource11

@resource11

Design Systems
and React@resource11

@resource11

@resource11

React is a
kitchen utensil
CC BY-SA 2.0: Jeppestown utensil

@resource11

You are the cook
CC BY-SA 2.0: Jeppestown utensil

@resource11

Empower with yourdesign system
CC BY-SA 2.0: Jeppestown utensil

@resource11

@resource11

Components are
your tried andtrue recipes

@resource11

WCAG is Your
reference material

@resource11

Component
recipe@resource11

Use high-quality ingredientssemantic HTML

@resource11

Mix in seasonings

ARIA …just a touch

@resource11

Follow the directions

documentation

@resource11

Read helpful hints

best practices

@resource11

Icons

@resource11

Icons
Informative or decorative

Informative icons
need descriptive text
Decorative icons
need to be hiddenfrom assistive
technology

@resource11

SVGs vs. Icon Fonts

VoiceOver
bugs make
SVG sadness

@resource11

Icon fonts
to the rescue

@resource11

Icon font pattern<span class="icon icon-email"></span>@resource11

Accessible icon pattern<span class="root"><span class="icon icon-email"aria-hidden="true"></span><span class="visuallyHidden">email</span></span>Icon font@resource11.icon {
color: currentColor;
font-family: "ORM Icons";}<span class="root"><span class="icon icon-email"aria-hidden="true"></span>.icon-email::before {<span</span>content:"\f12f";class="visuallyHidden">email</span>}@resource11

Hide icon font from screen readers<span class="root"><span class="icon icon-email"aria-hidden="true"></span>.icon-email::before {<span</span>content:"\f12f";class="visuallyHidden">email</span>}@resource11

Descriptive icon name<span class="root"><span class="icon icon-email"aria-hidden="true"></span><span class="visuallyHidden">email</span></span>@resource11

Visually-hidden text
<span class="root"><span class="icon icon-email".visuallyHidden {position: absolute;
overflow: hidden;
clip: rect(0 0 0 0);
height: 1px;
width: 1px;
margin: -1px;
padding: 0;
border: 0;}

aria-hidden="true"></span><span class="visuallyHidden">email</span></span>@resource11

Margin/padding
support<span class="root">.root {
display: inline-block;}<span class="icon icon-email"aria-hidden="true"></span><span class="visuallyHidden">email</span></span>@resource11

Spans all the way down
<span class="root"><span class="icon icon-email"aria-hidden="true"></span><span class="visuallyHidden">email</span></span>@resource11

Informative or decorative?
<span class="root"><span class="icon icon-email"aria-hidden="true"></span><span class="visuallyHidden">email</span></span>@resource11

Informative icons should announce<span class="root"><span class="icon icon-email"aria-hidden="true"></span><span class="visuallyHidden">email</span></span>@resource11

Hide decorative icons
from screen readers<span class=“root" aria-hidden="true"><span class="icon icon-email"aria-hidden="true"></span><span class="visuallyHidden">email</span></span>React functional component
export const Icon = props => {HTML syntax

return (<span class="root"><span class="icon icon-email” aria-hidden=”true” /><span class="visuallyHidden">email</span></span>);}export default Icon;@resource11

@resource11

export const Icon = props => {class
return (className“true”{true}<span className="root"><span className="icon icon-email” aria-hidden={true} /><span className="visuallyHidden">email</span></span>);}export default Icon;

Close empty span@resource11

JSX syntax
export const Icon = props => {return (<span className="root"><span className="icon icon-email” aria-hidden={true} /><span className="visuallyHidden">email</span></span>);}export default Icon;export const Icon = props => {const { iconHidden, iconName, iconTitle } = this.props;
const icon = Icons[iconName];if (!icon) return null;@resource11

return (<span className="root" aria-hidden={iconHidden ? true : null}><spanclassName={`icon icon-${iconName}`} aria-hidden={true}/><span className="visuallyHidden">{iconTitle || iconName}</span></span>);}export default Icon;

Support incoming
props, add guardrails

Guardrails@resource11

export const Icon = props => {const { iconHidden, iconName, iconTitle } = this.props;
const icon = Icons[iconName];if (!icon) return null;@resource11

return (<span className="root" aria-hidden={iconHidden ? true : null}><spanclassName={`icon icon-${iconName}`} aria-hidden={true}/><span className="visuallyHidden">{iconTitle || iconName}</span></span>);}export default Icon;

Icon naming
guardrails

export const Icon = props => {const { iconHidden, iconName, iconTitle } = this.props;
const icon = Icons[iconName];if (!icon) return null;@resource11

return (<span className="root" aria-hidden={iconHidden ? true : null}><spanclassName={`icon icon-${iconName}`} aria-hidden={true}/><span className="visuallyHidden">{iconTitle || iconName}</span></span>);}export default Icon;

Icon
description
guardrails

export const Icon = props => {const { iconHidden, iconName, iconTitle } = this.props;
const icon = Icons[iconName];if (!icon) return null;@resource11

return (<span className="root" aria-hidden={iconHidden ? true : null}><spanclassName={`icon icon-${iconName}`} aria-hidden={true}/><span className="visuallyHidden">{iconTitle || iconName}</span></span>);}export default Icon;

Icon Hiding
guardrails

export const Icon = props => {const { iconHidden, iconName, iconTitle } = this.props;
const icon = Icons[iconName];if (!icon) return null;@resource11

return (<span className="root" aria-hidden={iconHidden ? true : null}><spanclassName={`icon icon-${iconName}`} aria-hidden={true}/><span className="visuallyHidden">{iconTitle || iconName}</span></span>);}export default Icon;

Icon Hiding
guardrails

@resource11…but SVGs!

@resource11

@resource11

@resource11

@resource11<FontAwesomeIcon
icon=“coffee”/>

@resource11<svg
data-icon=“coffee” aria-hidden=“true”
focusable=“false”/><path>…</path></svg>

@resource11<FontAwesomeIcon
icon=“coffee”
ariaLabel=“beverage”/>

@resource11<FontAwesomeIcon
icon=“coffee”
ariaLabel=“beverage”/>

@resource11<svg
data-icon=“coffee” aria-hidden=“true”
focusable=“false” aria-label=“beverage”/><path>…</path></svg>

aria-hidden hides
from screen reader<svg@resource11

data-icon=“coffee” aria-hidden=“true”
focusable=“false” aria-label=“beverage”/><path>…</path></svg>

aria-label will
not announce

@resource11

export const Icon = props => {const { iconHidden, iconName, iconTitle } = this.props;
return (<span className=“root” aria-hidden={iconHidden ? true : null}><spanclassName={`icon icon-${iconName}`} aria-hidden={true}/><span className="visuallyHidden">{iconTitle || iconName}</span></span>);}export default Icon;

Swap out font-basedclassName

@resource11

export const Icon = props => {const { iconHidden, iconName, iconTitle } = this.props;
return (<span className="root" aria-hidden={iconHidden ? true : null}><FontAwesomeIcon
icon={iconName}/><span className="visuallyHidden">{iconTitle || iconName}</span></span>);}export default Icon;

Swap in FontAwesomeIcon

@resource11

export const Icon = props => {const { iconHidden, iconName, iconTitle } = this.props;
return (<span className="root" aria-hidden={iconHidden ? true : null}><FontAwesomeIcon
icon={iconName}/><span className="visuallyHidden">{iconTitle || iconName}</span></span>);}export default Icon;

Informative icon
naming supported

@resource11

Buttons

@resource11

Buttons

Perform an action on
the page
Should look and act
like a button
Screen reader andkeyboardfunctionality for free

@resource11

Button<button class="root">
Send an email</button>@resource11

ButtonSprinkle
some ARIA<button class="root" aria-label="Read more about Dinosaurs">
Read more</button>@resource11

ButtonSprinkle
some ARIA<button class="root" aria-label=“Read more about dinosaurs">
Read more</button>

Button text and aria label
start with same words

@resource11

ButtonSprinkle
some ARIA<button class="root" aria-label=“Read more about dinosaurs">
Read more</button>

Button text and aria label
start with same words

@resource11

Button<button className="root" aria-label="Read about Dinosaurs">
Read More</button>@resource11<button className="root" aria-label="Read about Dinosaurs>{children}<Icon
iconName={iconName}
iconHidden={true}/></button>

Add iconsupport@resource11<button className="root" aria-label="Read about Dinosaurs
onClick={onClick} disabled={disabled ? true : null}><span className="btnContentWrap">{children}<Icon
iconName={iconName}
iconHidden={true}/></span></button>

Wrap contentsin span@resource11
export const Button = props => {const { ariaLabel, children, disabled, iconName, onClick.}.= this.props;
return (<button className="root" aria-label={ariaLabel}
onClick={onClick} disabled={disabled ? true : null}><span className="btnContentWrap">{children}{iconName && (<Icon
iconName={iconName}
iconHidden={true}/>)}</span></button>);}

Add props, click
handler &disabled button
support@resource11
export const Button = props => {const { ariaLabel, children, disabled, iconName, onClick.}.= this.props;
return (<button className="root" aria-label={ariaLabel}
onClick={onClick} disabled={disabled ? true : null}><span className="btnContentWrap">{children}{iconName && (<Icon
iconName={iconName}
iconHidden={true}/>)}</span></button>);

Add guardrails}

No iconName?
No icon rendered@resource11

Inputs@resource11

Inputs needlabels anderror messages

Labeled inputs
give all users
more context

@resource11

Placeholders
are NOT labels

Avoid using
placeholders insteadof labels, users willlose context
Hard to style acrossbrowsers
Placeholders aren’t
auto translated

@resource11

Avoid Horizontal
Scrolling

Max input width:
80 characters
Keep labels stacked
vertically in close
proximity
Labels above input,
errors below input

@resource11<div className="root">{label && (<label htmlFor={id} className="inputLabel">{label}</label>{iconName && (<Icon
iconName={iconName}
iconHidden={true}/>)}<inputclassName={inputClasses}
id={id}name={id}type={type}value={value}
disabled={disabled ? true : null}onChange={onChange}
onKeyPress={onKeyPress}aria-describedby={invalid && error ? `error-${id}` : null}aria-invalid={invalid ? true : null}aria-required={required ? true : null}/>}{invalid &&error && (<div className="errorWrapper"><Icon iconName="warning-fill"
iconHidden={true} /><spanid={`error-${id}`}className="errorTxt"
aria-live="polite">{error}</span></div>}</div>

@resource11<div class=“root”><label for="inputFoo" class="inputLabel">First name</label><inputclass="inputClasses"
id=“inputFoo"
name="inputFoo"
type="text"/><span id=“errorFoo" class="errorTxt">
Whoops! Error.
</span></div><div className="root">{label && (<label htmlFor={id} className="inputLabel">{label}</label>{iconName && (<Icon
iconName={iconName}
iconHidden={true}/>)}<inputclassName={inputClasses}
id={id}name={id}type={type}value={value}
disabled={disabled ? true : null}onChange={onChange}
onKeyPress={onKeyPress}aria-describedby={invalid && error ? `error-${id}` : null}aria-invalid={invalid ? true : null}aria-required={required ? true : null}/>}{invalid &&error && (<div className="errorWrapper"><Icon iconName="warning-fill"
iconHidden={true} /><spanid={`error-${id}`}className="errorTxt"
aria-live="polite">{error}</span></div>}</div>

SemanticHTML@resource11<div class=“root”><label for="inputFoo" class="inputLabel">First name</label><inputclass="inputClasses"
id=“inputFoo"
name="inputFoo"
type="text"/><span id=“errorFoo" class="errorTxt">
Whoops! Error.
</span></div><div className="root">{label && (<label htmlFor={id} className="inputLabel">{label}</label>{iconName && (<Icon
iconName={iconName}
iconHidden={true}/>)}<inputclassName={inputClasses}
id={id}name={id}type={type}value={value}
disabled={disabled ? true : null}onChange={onChange}
onKeyPress={onKeyPress}aria-describedby={invalid && error ? `error-${id}` : null}aria-invalid={invalid ? true : null}aria-required={required ? true : null}/>}{invalid &&error && (<div className="errorWrapper"><Icon iconName="warning-fill"
iconHidden={true} /><spanid={`error-${id}`}className="errorTxt"
aria-live="polite">{error}</span></div>}</div>

Labelsand error
messages@resource11<div className=“root”><label htmlFor="inputFoo" className="inputLabel">First name</label><inputclassName="inputClasses"
id="inputFoo"
name="inputFoo"
type="text"/><span id=“errorFoo" className="errorTxt">
Whoops! Error.
</span></div><div className="root">{label && (<label htmlFor={id} className="inputLabel">{label}</label>{iconName && (<Icon
iconName={iconName}
iconHidden={true}/>)}<inputclassName={inputClasses}
id={id}name={id}type={type}value={value}
disabled={disabled ? true : null}onChange={onChange}
onKeyPress={onKeyPress}aria-describedby={invalid && error ? `error-${id}` : null}aria-invalid={invalid ? true : null}aria-required={required ? true : null}/>}{invalid &&error && (<div className="errorWrapper"><Icon iconName="warning-fill"
iconHidden={true} /><spanid={`error-${id}`}className="errorTxt"
aria-live="polite">{error}</span></div>}</div>

Associatelabel withinput

@resource11<div className="root">{label && (<label htmlFor={id} className="inputLabel">{label}</label>{iconName && (<Icon
iconName={iconName}
iconHidden={true}/>)}<inputclassName={inputClasses}
id={id}name={id}type={type}value={value}
disabled={disabled ? true : null}onChange={onChange}
onKeyPress={onKeyPress}aria-describedby={invalid && error ? `error-${id}` : null}aria-invalid={invalid ? true : null}aria-required={required ? true : null}/>}{invalid &&error && (<div className="errorWrapper"><Icon iconName="warning-fill"
iconHidden={true} /><spanid={`error-${id}`}className="errorTxt"
aria-live="polite">{error}</span></div>}</div><div className="root"><label htmlFor="inputFoo" className="inputLabel">First name</label><inputclassName="inputClasses"
id=“inputFoo"
name=“inputFoo"
type="text"
aria-describedby=“errorFoo"
aria-invalid="true"aria-required="true"/><span id="errorFoo" className="errorTxt">
Whoops! Error.
</span></div>

Sprinkle
some ARIA

@resource11<div className="root">{label && (<label htmlFor={id} className="inputLabel">{label}</label>{iconName && (<Icon
iconName={iconName}
iconHidden={true}/>)}<inputclassName={inputClasses}
id={id}name={id}type={type}value={value}
disabled={disabled ? true : null}onChange={onChange}
onKeyPress={onKeyPress}aria-describedby={invalid && error ? `error-${id}` : null}aria-invalid={invalid ? true : null}aria-required={required ? true : null}/>}{invalid &&error && (<div className="errorWrapper"><Icon iconName="warning-fill"
iconHidden={true} /><spanid={`error-${id}`}className="errorTxt"
aria-live="polite">{error}</span></div>}</div><div className="root"><label htmlFor="inputFoo" className="inputLabel">First name</label><inputclassName="inputClasses"
id="inputFoo"
name="inputFoo"
type="text"
aria-describedby="errorFoo"
aria-invalid="true"aria-required="true"/><span id="errorFoo" className="errorTxt" aria-live="polite">
Whoops! Error.
</span></div>

<div className="root"><label htmlFor={id} className="inputLabel">{label}<div className="root"></label>{label && (<label htmlFor={id} className="inputLabel">{label}</label><input{iconName && (<IconclassName={inputClasses}
iconName={iconName}
id={id}iconHidden={true}/>
name={id})}<inputtype={type}className={inputClasses}
id={id}value={value}
name={id}type={type} disabled={disabled ? true : null}value={value}
onChange={onChange}
disabled={disabled ? true : null}onChange={onChange}
onKeyPress={onKeyPress}
onKeyPress={onKeyPress}aria-describedby={invalid&& error ? `error-${id}` : null}&& error ? `error-${id}` : null}aria-describedby={invalid
aria-invalid={invalid ? true : null}aria-invalid={invalid? true : null}aria-required={required? true : null}/>aria-required={required ? true : null}}{invalid &&/>error && (<span
<div className="errorWrapper"><Icon iconName="warning-fill"id={`error-${id}`}
iconHidden={true}/><spanclassName="errorTxt"
id={`error-${id}`}className="errorTxt"
aria-live="polite"
aria-live="polite">>{error}{error}</span></div> </span>}</div></div>@resource11

disabled and synthetic
event support

and true/nullhandling

{label && (<label htmlFor={id} className="inputLabel">{label}<div className="root"></label>{label && (<label htmlFor={id} className="inputLabel">{label}</label>{iconName && ({iconName && (
<Icon
<Icon
iconName={iconName}
iconHidden={true}iconName={iconName}/>
iconHidden={iconHidden})}<inputtitle={iconTitle}className={inputClasses}
id={id}/>name={id})}type={type}value={value}<inputdisabled={disabled ? true : null}onChange={onChange}className={inputClasses}
onKeyPress={onKeyPress}aria-describedby={invalid&& error ? `error-${id}` : null}
id={id}aria-invalid={invalid ? true : null}
name={id}aria-required={required? true : null}/>
type={type}}{invalid &&value={value}error && (disabled={disabled ? true : null}<div className="errorWrapper"><Icon iconName="warning-fill"
onChange={onChange}
iconHidden={true}/><span
onKeyPress={onKeyPress}id={`error-${id}`}className="errorTxt"
aria-describedby={invalid && error ? `error-${id}` : null}aria-live="polite"
aria-invalid={invalid ? true : null}>{error}aria-required={required ? true : null}</span></div> />}</div>)}@resource11

Add guardrails

@resource11<div className="root">{label && (<label htmlFor={id} className="inputLabel">{label}</label>{iconName && (<Icon
iconName={iconName}
iconHidden={true}/>)}<inputclassName={inputClasses}
id={id}name={id}type={type}value={value}
disabled={disabled ? true : null}onChange={onChange}
onKeyPress={onKeyPress}aria-describedby={invalid && error ? `error-${id}` : null}aria-invalid={invalid ? true : null}aria-required={required ? true : null}/>}{invalid &&error && (<div className="errorWrapper"><Icon iconName="warning-fill"
iconHidden={true} /><spanid={`error-${id}`}className="errorTxt"
aria-live="polite">{error}</span></div>}</div>{invalid &&error && (<div className="errorWrapper"><Icon iconName="warning-fill"
iconHidden={true} /><spanid={`error-${id}`}className="errorTxt"
aria-live="polite">{error}</span></div>)}

Add guardrails

@resource11

Disclosure
Widgets

@resource11

Disclosure
Widgets

Flexible pattern
Keyboard andmouse operable
Shared basefunctionality

@resource11

Menus & Toggle Tips

@resource11

Tooltips are NOT
Toggle Tips

@resource11

Tooltips can beproblematic

@resource11

Primary
keyboard
interactions

Space bar/Enter key
Escape key
Mouse clicks

@resource11

Targeted focus
management

@resource11

Disclosure
WidgetsDemo@resource11

Document,
document,
document

@resource11+

@resource11

@resource11

@resource11

@resource11

@resource11

Helpful hints
Informative Icon

@resource11

Helpful hints
Decorative Icon

@resource11

Helpful hints
Prop tables

@resource11

Helpful hints
Component dos/don’ts

@resource11

Dedicate a pageto accessibility
resources

@resource11

Wrap-upOur users are diverse
VisionSensory

Hearing

Language

Inclusive Design Persona Spectrum (CC BY-NC-SA 2.0)

Motor

Cognitive

Low bandwidth

@resource11

Your Design
system is acookbook

@resource11

Cookbooks have
Personality

@resource11

React is a
kitchen utensil
CC BY-SA 2.0: Jeppestown utensil

@resource11

You are the cook
CC BY-SA 2.0: Jeppestown utensil

@resource11

Components are
your tried andtrue recipes

@resource11

WCAG is Your
reference material

@resource11

Document,
document,
document

@resource11

Dinosaurs are
ALWAYS the
hotness

@resource11

Thank you.

@resource11

https://noti.st/resource11
Slide deck posted after the talk@resource11
Twitter | Instagram | GitHub