Skip to content

Proposal for triggerRef option and ARIA attribute automation to improve A11y #177

@jun094

Description

@jun094

Hello, I'm a developer who has found overlay-kit to be incredibly useful. The declarative API has made managing overlay state a much more pleasant experience. I'd like to propose an enhancement to further this developer experience, specifically around accessibility.

The Problem

A core value of overlay-kit is abstracting away the boilerplate of overlay state management, as shown in the Code Comparison documentation.

However, to implement an accessible overlay, developers are still required to write repetitive boilerplate code to manage ARIA attributes.

Current Implementation:

function CurrentExample() {
  // 1. Separate state needed for aria-expanded
  const [isExpanded, setIsExpanded] = useState(false);
  const dialogId = useId(); // 2. Manual ID linking

  const handleOpen = () => {
    setIsExpanded(true);
    overlay.open(({ isOpen, close }) => (
      <Dialog
        id={dialogId}
        open={isOpen}
        onClose={() => {
          close();
          setIsExpanded(false); // 3. Manually update state on close
        }}
      />
    ));
  };

  return (
    <button
      onClick={handleOpen}
      aria-expanded={isExpanded}
      aria-controls={dialogId}
    >
      Open Dialog
    </button>
  );
}

This manual management can lead to mistakes, and is a bit of a departure from the simplicity that overlay-kit aims for.

Proposed Solution

I propose enhancing the overlay.open API to accept an optional triggerRef. When this ref is provided, overlay-kit would automatically manage the necessary ARIA attributes.

  • Proposed API: overlay.open(Controller, { triggerRef?: React.RefObject<Element> })
  • How it would work:
    1. When triggerRef is provided, overlay-kit internally generates an ID for the overlay and links it to the triggerRef element's aria-controls attribute.
    2. When the overlay is opened, it sets aria-expanded="true" on the triggerRef element.
    3. When the overlay is closed, it sets aria-expanded="false".

The Ideal API

This feature would allow developers to forget about ARIA attribute management and focus purely on their component's logic, resulting in cleaner and more declarative code.

Proposed Implementation:

function ProposedExample() {
  const triggerRef = useRef<HTMLButtonElement>(null);

  const handleOpen = () => {
    overlay.open(
      ({ isOpen, close }) => <Dialog open={isOpen} onClose={close} />,
      { triggerRef } // Just pass the ref, and accessibility is handled!
    );
  };

  return (
    <button ref={triggerRef} onClick={handleOpen}>
      Open Dialog
    </button>
  );
}

Benefits

  • Eliminates boilerplate, allowing developers to be more productive.
  • Automates ARIA implementation, reducing human error and ensuring a higher standard of accessibility out of the box.
  • Extends the library's core value from "simplified state management" to a more holistic "simplified UI declaration."

This would be a non-breaking change, as the triggerRef option would be entirely opt-in. I believe this addition would significantly increase the value of overlay-kit.

I would love to hear the maintainers' thoughts on this proposal. I am also willing to contribute to a PR if this aligns with the project's roadmap.

Thank you!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions