I'm trying to create an Accordion component with WAI-ARIA attributes, however, some elements are required to reference a unique id. In this case, the Accordion's content (accordion__content
) has to have a unique id that is referenced by the button's attribute aria-controls
.
Since the component might be used multiple times in different places within the page, using 'any-id' as an id would not work.
type AccordionProps = {
children: ReactNode;
label: string;
};
export const Accordion = ({ label, children }: AccordionProps) => {
const { isContentShowing, setIsContentShowing } = useAccordion();
return (
<div className="accordion" aria-label={`${label} Accordion`} data-testid="accordion">
<button
className="accordion__button"
onClick={setIsContentShowing}
aria-expanded={isContentShowing}
aria-controls="UNIQUE-ID"
>
<span className="accordion__text">{label}</span>
<AccordionArrowIcon isContentShowing={isContentShowing} />
</button>
{isContentShowing && (
<div className="accordion__content" id="UNIQUE-ID" aria-hidden={!isContentShowing}>
{children}
</div>
)}
</div>
);
};
One option would be to generate a random id and just assign that id to the attributes, or we could also take the id as a prop. Is there any recommended way for doing this?
When I've had problems like this in the past, I've generated a uuid for the component. That would make your code like this:
import React from "react";
// This is the library I'd use, but feel free to make up the ID however you want
import { uuid } from 'uuidv4';
type AccordionProps = {
children: ReactNode;
label: string;
};
export const Accordion = ({ label, children }: AccordionProps) => {
const { isContentShowing, setIsContentShowing } = useAccordion();
// Note that the ID will be regenerated on every re-render, but will still stay internally consistent
const id = uuid();
return (
<div className="accordion" aria-label={`${label} Accordion`} data-testid="accordion">
<button
className="accordion__button"
onClick={setIsContentShowing}
aria-expanded={isContentShowing}
aria-controls={id}
>
<span className="accordion__text">{label}</span>
<AccordionArrowIcon isContentShowing={isContentShowing} />
</button>
{isContentShowing && (
<div className="accordion__content" id={id} aria-hidden={!isContentShowing}>
{children}
</div>
)}
</div>
);
Also, your aria-hidden
isn't doing anything. There's logically no point where it would be true, since the element doesn't exist when the content isn't showing.