The forms boss battle
... and how to avoid it
- Almero Steyn
- almerosteyn.com
- @kryptos_rsa
... and how to avoid it
Username
Password
Login
If it looks like a button and quacks like a button... what should it be?
Login
Username
The <span> does NOT label the <input>!
In other words this <input> has no accessible name
<input id="username" />
Username
Implicit labelling, explicit labelling, aria-label and
aria-labelledby
Explicit labelling
<input id="username" />
Accessibility evaluation of the top 1 000 000 home pages
59% of the 3.4 million form inputs identified were unlabeled (either via <label>, aria-label, or aria-labelledby). - https://webaim.org/projects/million
A control should have only one name.
<input id="username" aria-label="Enter a name for your user"/>
<input id="username" aria-describedby="info"/>
Please use only characters from the alphabet
<input id="username" aria-describedby="info error"/>
Please use only characters from the alphabet
A user name is required
That means also for <textarea>,
<input type="checkbox"> and <select>
Here we group with <fieldset>
and label with <legend>
The <legend> tag HAS to be immediately after the <fieldset> tag!
Attaching aria-describedby to <fieldset> or <legend> is not supported in many screen readers
Add text to the legend.
This means it can also be used for info texts!
Move with CSS... or:
.visually-hidden:not(:focus):not(:active) {
clip: rect(1px 1px 1px 1px);
clip-path: inset(100%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
Don't do this! Unless you provide your own focus outline style in the CSS.
outline: none;
outline: 0;
Should be avoided in most cases!
All video example apps were built in React...
A few differences:
To build accessible React applications you HAVE to know HTML.
const FormComponent = () => {
const onSubmitHandler = e => {
e.preventDefault();
//Rest of the handler code
};
return (
<form onSubmit={onSubmitHandler}>
{/* Form JSX */}
</form>
);
};
const inputRef = React.useRef(null);
const onSubmitHandler = e => {
//Set focus on error condition
inputRef.current.focus();
};
return (
<form onSubmit={onSubmitHandler}>
{/* Some JSX */}
<input ref={inputRef} />
{/* Some more JSX */}
</form>
);
const [inputValue, setInputValue] = React.useState('');
onChangeHandler = e => {
setInputValue(e.target.value);
}
return (
<React.Fragment>
<input id="petName"
onChange={onChangeHandler}
value={inputValue} />
</React.Fragment>
);
const inputId = React.useRef(generateUniqueId());
const [inputValue, setInputValue] = React.useState('');
onChangeHandler = e => {
setInputValue(e.target.value);
}
return (
<React.Fragment>
<label htmlFor={inputId.current}>Your pet's name</label>
<input id={inputId.current} onChange={onChangeHandler}
value={inputValue} />
</React.Fragment>
);
const inputId = React.createRef(generateUniqueId());
const errorId = React.createRef(generateUniqueId());
//More code
return (
<React.Fragment>
<label htmlFor={inputId.current}>Your pet's name</label>
<input id={inputId.current}
aria-describedby={hasError ? errorId : null}
onChange={onChangeHandler}
value={inputValue} />
{hasError && <span id={errorId}>Please enter a name</span>}
</React.Fragment>
);
const useInput = () => {
const inputId = React.createRef(generateUniqueId());
const [inputValue, setInputValue] = React.useState('');
onChangeHandler = e => {
setInputValue(e.target.value);
}
return {
id: inputId,
value: inputValue
onChange: onChangeHandler
}
}
const inputProps = useInput();
return (
<React.Fragment>
<label htmlFor={inputProps.id}>Your pet's name</label>
<input {...inputProps} />
</React.Fragment>
);
... and how to avoid it