Accordion

Table of Contents

For this accordion, I used the Accordion (Sections with Show/Hide Functionality)-design pattern Opens an external site from WAI-ARIA Authoring Practices. I've written a blog post Opens an external site explaining the pattern a bit more.

This Accordion component takes a title as props and displays the children of the component as the content

In WAI-ARIA Design Patterns, the keyboard shortcuts and ARIA states, properties and roles are defined. I'll introduce them in the next sections. Then I'll show the source code for the Accordion-component.

Keyboard Shortcuts

Keyboard shortcuts for accordion
KeyBehaviour
Enter or SpacebarWhen focus is on the accordion header, opens or closes that accordion.
TabMoves focus for the next focusable element.
Shift + TabMoves focus for the next previous element.

There are also optional keyboard shortcuts, which will not be implemented in this example:

Optional keyboard shortcuts for accordion
KeyBehaviour
Up ArrowWhen focused to an accordion header, used for navigating from that header to the next
Down ArrowWhen focused to an accordion header, used for navigating from that header to the previous.
HomeWhen focused to an accordion header, used for navigating to the first accordion header.
EndWhen focused to an accordion header, used for navigating to the last accordion header.

ARIA-states, properties, and roles

Each accordion header's title should be contained within an element with a `button '-role. In the example, this is done with a button-element.

This button-element should be then wrapped into an element that has the role of heading and aria-level set. In the example, we use an h2 element. There should be anything else except the button inside the heading. The header's button element should have aria-disabled set to true if the panel associated with it is open and not closable.

The last thing for the accordion header is an aria-expanded-attribute set to the button. Its value depends on the accordion panel's visibility; if it is visible, then the value is true, and if not, then false.

WAI-ARIA Authoring Practices mention adding aria-controls with the id of the accordion panel. There is, however, something to note about the aria-controls; it is currently supported only in JAWS.

The accordion panels' containers can have a role region and aria-labelledby referencing the id of the button in the accordion header. However, note that if there are more than approximately six accordions expandable simultaneously, the region-role shouldn't be used. Too many regions on the same page are too much.

Source Code

Accordion

interface AccordionProps {
  title: string;
}

const Accordion: FunctionComponent<AccordionProps> = ({ title, children }) => {
  const [isOpen, setIsOpen] = useState(false);

  const handleOpen = () => setIsOpen(!isOpen);

  return (
    <section className="accordion-container">
      <div id="accordion-title">
        <h2>
          <button
            id="accordion-button"
            aria-expanded={isOpen}
            onClick={handleOpen}
            aria-controls="accordion-content"
          >
            <span>{isOpen ? "-" : "+"} </span>
            {title}
          </button>
        </h2>
      </div>
      <div
        id="accordion-content"
        aria-labelledby="accordion-button"
        hidden={!isOpen}
        role="region"
      >
        {children}
      </div>
    </section>
  );
};

CSS

#accordion-content {
  padding: 1rem 1.5rem;
  border-top: 0.1rem solid var(--color-text);
  background-color: var(--color-bakcground);
}

#accordion-button {
  background-color: transparent;
  border-style: none;
  padding: 1rem 1.5rem;
  font-size: 1.5rem;
  width: 100%;
  text-align: left;
  display: block;
  margin: 0;
  color: var(--color-text);
}

.accordion-container {
  border: 0.2rem solid var(--color-text);
  width: 25rem;
  max-width: 90%;
  font-size: 1.5rem;
}
#accordion-title {
  outline: none;
  background-color: var(--color-bakcground);
}
#accordion-title > h2 {
  margin: 0;
  padding: 0;
  outline: none;
}