The forms boss battle

... and how to avoid it


Logo of Binary Horizons
  • React
  • Accessibility

1977

Atari gaming console
Wikimedia Commons licensed under CC BY 2.0

2019

Playstation 4 gaming console
Released into the Public Domain

The game boss

Screenshot of a console game with a large, scary game boss
Wikimedia Commons licensed under CC BY 2.0

It's too difficult!!!

Woman being angry about a difficult game
Licensed under the Pixabay License

Boss battles on the web

A basic login form with username, password and a login button

An inaccessible login form

How can something so simple be so broken?


    Username
    
    Password
    
    
Login

About that "button"...

If it looks like a button and quacks like a button... what should it be?


    
Login

How about labels?


    Username
    

The <span> does NOT label the <input>!

In other words this <input> has no accessible name

How do I give an input an accessible name?





<input id="username" />



Username
   

Implicit labelling, explicit labelling, aria-label and

aria-labelledby

And the inclusive design winner:

Explicit labelling



<input id="username" />

An accessible login form

The WebAIM Million

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

We need to share form accessibility secrets

Don't combine names!

A control should have only one name.



<input id="username" aria-label="Enter a name for your user"/>
  • A control should have only one name.
  • aria-label overrides the label text.
  • Think about voice control.

What about info texts?

A login form showing info text for the username field asking for only characters from the alphabet

Meet aria-describedby



<input id="username" aria-describedby="info"/>


    Please use only characters from the alphabet

Info text in action

Client side validation

A login form showing required field validation messages

Aria-describedby to the rescue again!



<input id="username" aria-describedby="info error"/>


    Please use only characters from the alphabet

A user name is required

Validation in action

This works for all single form elements

That means also for <textarea>,

<input type="checkbox"> and <select>

What about groups of controls?

Here we group with <fieldset>

and label with <legend>


Choose your favourite time of day

The <legend> tag HAS to be immediately after the <fieldset> tag!

But I can still set validation texts with aria-describedby, right?

Road sign with multiple arrows in multiple directions all ending up at the word NO
CC0 Public Domain

Attaching aria-describedby to <fieldset> or <legend> is not supported in many screen readers

How do I validate groups then?


Choose your favourite time of day You have not chosen a favourite time

Add text to the legend.

This means it can also be used for info texts!

You don't want your error texts next to your legends?

Move with CSS... or:


Choose your favourite time of day You have not chosen a favourite time //...

Visually hiding things


.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;
}

A fieldset in action

Never lose the focus outline

Road sign with multiple arrows in multiple directions all ending up at the word NO

Don't do this! Unless you provide your own focus outline style in the CSS.


    outline: none;
    outline: 0;

Placeholders and titles

Showing an input with a placeholder and a button with icon only and a title

Should be avoided in most cases!

Wasn't this talk supposed to be about React?

React logo

All video example apps were built in React...

JSX has everything HTML has

A few differences:

  • Tags should always be closed
  • Non-ARIA properties are in camelCase
  • for becomes htmlFor
  • class becomes className

To build accessible React applications you HAVE to know HTML.

React hooks

Boxer performing a hook
Licensed under the Pixabay License

Preventing form submission


const FormComponent = () => {

  const onSubmitHandler = e => {
    e.preventDefault();
    //Rest of the handler code
  };
  
  return (
      <form onSubmit={onSubmitHandler}>
        {/* Form JSX */}
      </form>
  );
  
};

Setting focus on things


  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>
  );

Controlled form elements


  const [inputValue, setInputValue] = React.useState('');  
  
  onChangeHandler = e => {
      setInputValue(e.target.value);
  }
  
  return (
    <React.Fragment>
         
         <input id="petName" 
            onChange={onChangeHandler}
            value={inputValue} />
    </React.Fragment>    
  );

Keeping id values unique


  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>    
  );

Conditional rendering


  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>    
  );

Re-using stuff with hooks


  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
    }
  }

Now apply the custom hook


  const inputProps = useInput();
  
  return (
    <React.Fragment>
         <label htmlFor={inputProps.id}>Your pet's name</label>
         <input {...inputProps} />
    </React.Fragment>    
  );

Applying it all

The Forms Boss Battle

... and how to avoid it


Logo of Binary Horizons