DEV Community

David Bell
David Bell

Posted on

Make an accessible search bar in React

Make an accessible search bar in React

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

Enter fullscreen mode Exit fullscreen mode

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

Type search

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'
          />
Enter fullscreen mode Exit fullscreen mode

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'
          />
Enter fullscreen mode Exit fullscreen mode

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'
      />
Enter fullscreen mode Exit fullscreen mode

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'
          />
Enter fullscreen mode Exit fullscreen mode

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'
          />
Enter fullscreen mode Exit fullscreen mode

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
          />
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode
const handleSubmit = e => {
    e.preventDefault();
    setSearchedResult(term);
    setTerm('');
    if (filteredItems.length === 0) {
      setError('No items matching your search please be more specific');
    }
  };
Enter fullscreen mode Exit fullscreen mode
// className can be used to hide this
  <span className='errMsg' role='status'>
  {error}
  </span>
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

Useful links to read more.

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.

Oldest comments (0)