Make an accessible search bar in React

ยท

5 min read

This post is going to explain how to take a basic search input and make it accessible to everyone.

Let's begin

I have began by creating a simple app. It contains a header with a search input and navigation links. In the main of the app all the shopping items are displayed. You can search for items in the search input and items that contain the character you searched for are displayed.

import { useState } from 'react';
import styles from '../styles/Home.module.css';

export default function Home() {
  const [term, setTerm] = useState('');
  const [searchedResult, setSearchedResult] = useState('');

  const items = [
    { name: 'apple' },
    { name: 'banana' },
    { name: 'pear' },
    { name: 'apple tango' },
    { name: 'mango' },
    { name: 'banana milkshake' },
  ];

  const handleSubmit = e => {
    e.preventDefault();
    setSearchedResult(term);
    setTerm('');
  };

  const handleChange = e => {
    setTerm(e.target.value);
  };

  const filteredItems = items.filter(item =>
    item.name.toLocaleLowerCase().includes(searchedResult.toLocaleLowerCase())
  );

  return (
    <div className={styles.container}>
      <header className={styles.header}>
        <div>
          <a href='/'>LOGO</a>
        </div>
        <form className={styles.search} onSubmit={handleSubmit}>
          <input
            type='text'
            id='search'
            name='search'
            onChange={handleChange}
            value={term || ''}
            placeholder='search'
          />
          <button type='submit' onClick={handleSubmit}>
            Search
          </button>
        </form>
        <nav className={styles.nav}>
          <div>
            <a href='/'>About</a>
          </div>
          <div>
            <a href='/'>Contact</a>
          </div>
        </nav>
      </header>
      <main className={styles.main}>
        <h3>Items</h3>
        {filteredItems && (
          <ul>
            {filteredItems.map(item => (
              <li key={item.name}>{item.name}</li>
            ))}
          </ul>
        )}
      </main>
    </div>
  );
}

This app works but it's not accessible. Which means it is no good. This app should be accessible to everyone and anyone.

Making the search functionality accessible

First we can add a role of search to the form. This will help assistive technology to inform the user that there is a search option. Screen readers will be given the option to navigate to this section.

Search role

<form role='search' className={styles.search} onSubmit={handleSubmit}>

Modern browsers support an input type of "search". This works just like type="text" but with added benefits. It helps with autocomplete options which is useful for users with dyslexia to avoid spelling mistakes when they need to use this option.

Changes to make:

  <input
        type='search'
        id='search'
        name='search'
        onChange={handleChange}
        value={term || ''}
        placeholder='search'
          />

Labelling

For user controlled elements like the search input we have, needs a <label>. Screen readers will read this label out to the user.

   <label htmlFor='search'>Search:</label>
          <input
            type='search'
            id='search'
            name='search'
            onChange={handleChange}
            value={term || ''}
            placeholder='search'
          />

The above code will display the label in the browser and be visible. Sometimes you might not want the label to be visible though. You must include a label but you can hide it using CSS but you must include the htmlFor attribute.

<label htmlFor='search' className='visualy-hidden'>Search:</label>
  <input
    type='search'
    id='search'
    name='search'
    onChange={handleChange}
    value={term || ''}
    placeholder='search'
      />

You can add an aria-label direct to the input if you do not wish to use a label element. This will be read out by screen readers.

 <label htmlFor='search'>Search:</label>
          <input
            aria-label='Enter your search term'
            type='search'
            id='search'
            name='search'
            onChange={handleChange}
            value={term || ''}
            placeholder='search'
          />

An additional step would be to app a title to the input.

 <label htmlFor='search'>Search:</label>
          <input
            title='search for food'
            aria-label='Enter your search term'
            type='search'
            id='search'
            name='search'
            onChange={handleChange}
            value={term || ''}
            placeholder='search'
          />

If you're interested diving deeper this article here goes deeper.

Validation

Our form needs validation to alert the user if there is any issues.

First we can add a required field to make help make sure the search functions has been typed in.

 <label htmlFor='search'>Search:</label>
          <input
            title='search for food'
            aria-label='Enter your search term'
            type='search'
            id='search'
            name='search'
            onChange={handleChange}
            value={term || ''}
            placeholder='search'
            required
          />

We can also add error handling of our own. You can hide this with CSS.

I'm going to use error state and display a span containing the error message.

const [error, setError] = useState(null);
const handleSubmit = e => {
    e.preventDefault();
    setSearchedResult(term);
    setTerm('');
    if (filteredItems.length === 0) {
      setError('No items matching your search please be more specific');
    }
  };
// className can be used to hide this
  <span className='errMsg' role='status'>
  {error}
  </span>

Heres the code so far:

export default function Home() {
  const [term, setTerm] = useState('');
  const [searchedResult, setSearchedResult] = useState('');
  const [error, setError] = useState(null);

  const items = [
    { name: 'apple' },
    { name: 'banana' },
    { name: 'pear' },
    { name: 'apple tango' },
    { name: 'mango' },
    { name: 'banana milkshake' },
  ];

  const handleSubmit = e => {
    e.preventDefault();
    setSearchedResult(term);
    setTerm('');
    if (filteredItems.length === 0) {
      setError('No items matching your search please be more specific');
    }
  };

  const handleChange = e => {
    setTerm(e.target.value);
  };

  const filteredItems = items.filter(item =>
    item.name.toLocaleLowerCase().includes(searchedResult.toLocaleLowerCase())
  );

  return (
    <div className={styles.container}>
      <header className={styles.header}>
        <div>
          <a href='/'>LOGO</a>
        </div>
        <form role='search' className={styles.search} onSubmit={handleSubmit}>
          <label htmlFor='search'>Search:</label>
          <input
            title='search for food'
            aria-label='Enter your search term'
            type='search'
            id='search'
            name='search'
            onChange={handleChange}
            value={term || ''}
            placeholder='search'
            required
          />

          <button type='submit' onClick={handleSubmit}>
            Search
          </button>
        </form>
        <nav className={styles.nav}>
          <div>
            <a href='/'>About</a>
          </div>
          <div>
            <a href='/'>Contact</a>
          </div>
        </nav>
      </header>
      <main className={styles.main}>
        <h3>Items</h3>
        <span className='errMsg' role='status'>
          {error}
        </span>
        {filteredItems && (
          <ul>
            {filteredItems.map(item => (
              <li key={item.name}>{item.name}</li>
            ))}
          </ul>
        )}
      </main>
    </div>
  );
}

Our forms looking good. There is more we can do regarding error messages etc but this is just to give a good idea and to start thinking about it.

Summary

To summarise you need to realise that there is users that have different requirements to yourself. You need to ensure that they are aware of all useful content within your web page.

๐Ÿšจ Attention! ๐Ÿšจ

If you know something that could be added to this article please comment. Improving site accessibility as a web dev community is for the greater good.