Skip to content

[feat] Enable setting focus on Accordion button #3447

@josephine-njooi

Description

@josephine-njooi

Does your feature request relate to a specific USWDS component?

Accordion https://github.com/trussworks/react-uswds/blob/main/src/components/Accordion/Accordion.tsx

What USWDS Version is this feature present in?

This feature does not exist as of v11

Is your feature request related to a problem? Please describe.

Yes. If an id attribute is set on an HTML element, one can use <a href="#idToLinkTo"> to create a link that jumps to that part of the page. Or if one is able to set a React ref on an component, then one can use the ref to focus on the element in a way that stays within the React system.

We wanted to add links on the page that would jump to the button of an Accordion. However, it is not possible to set the id attribute on the button in <Accordion />, and there is no forwarded ref to the button in <Accordion />.

There is an id available on Accordion contents. However, because the according contents do not exist when they are collapsed, a link to that id does not do anything if the Accordion is collapsed.

As a workaround, we ended up using plain non-React-controlled javascript (document.querySelector) to select the Accordion button, then focus on it. This is not ideal because doing manual, non-React DOM stuff can clash with React. (In this work around, at least we're only reading the DOM, not changing it).

Additionally, this workaround was not ideal because it required us to strong-arm the <a> tag, and set an onclick to focus on the Accordion button, then event.preventDefault() to disable the default linking behavior. Using <a> in this custom way runs the risk of causing accessibility issues.

Describe the solution you'd like

Any of:

  • The ability to set an id on Accordion buttons
  • An id present on Accordion buttons
  • The ability to get a ref on Accordion buttons
  • Any other solution that would enable focusing on an Accordion button

Describe alternatives you've considered

See the snippet in "Additional context" below for a component we made to work around this. This component also has the additional functionality of expanding the Accordion in question, without changing whether any other accordions are expanded (changing expanded in the items prop would reset any internal expanded/collapsed state that had happened since the initial render).

Additional context

For a live example (as of writing) of the behavior that would help if it were natively possible, go to https://doula-assistant.nj.gov/, and click on "Sole Proprietor" in "You are an individual doula operating as a Sole Proprietor." It should focus on an FAQ button lower down in the page.

Image Image

Code snippet used to work around not having a ref or id:

// LinkToExpandedAccordion.tsx

type LinkToExpandedAccordionProps = {
  accordionId: string;
  children: React.ReactNode;
};

const LinkToExpandedAccordion = (props: LinkToExpandedAccordionProps) => {
  /**
   * There is an id to the react-uswds AccordionItem contents, but the contents element does not
   * exist when not expanded. react-uswds does not expose props that enable us to put an id on the
   * button itself (not the contents). This means that we cannot use the HTML-native
   * href="#elementId" to focus on the target FAQ.
   *
   * Additionally, react-uswds does not expose ref to individual buttons within the <Accordion />
   * component. This makes it impossible to get a react-native ref on the element.
   *
   * Thus, we manually query and focus the target FAQ button using the onclick. Confirmed that the
   * onclick can be triggered by an enter key.
   *
   * The href attribute is not in use, due to the `event.preventDefault()` in the onclick (as
   * mentioned above, the id is for the contents, not the button). In theory we could expand the
   * contents then allow the default behavior of linking to the contents, but it is a better user
   * experience link to the button (so that the question is in view). However, even though href
   * attribute is not used for its usual linking functionality, the attribute is helpful have for
   * styling, and for hovering to get a sense of where the link is going to. Setting `href=""` will
   * make the link always look like it has been visited.
   *
   * This link does not indicate at the id anchor has already been visited, but that is the behavior
   * for all id anchor href destinations.
   */
  return (
    <a
      href={`#${props.accordionId}`}
      onClick={(event) => {
        const accordionButton = document.querySelector<HTMLInputElement>(
          `button[aria-controls='${props.accordionId}']`,
        );
        if (accordionButton) {
          accordionButton.focus();
          if (accordionButton.getAttribute("aria-expanded") == "false") {
            accordionButton.click();
          }
        } else {
          throw new Error(`Could not find accordion button with id ${props.accordionId}`)
        }
        event.preventDefault();
      }}
    >
      {props.children}
    </a>
  );
};

export default LinkToExpandedAccordion;

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions