DEV Community

Cover image for Easy Accessible Click Handlers
Andreas Riedmüller
Andreas Riedmüller

Posted on • Edited on

Easy Accessible Click Handlers

If you add a click event to a non-interactive html element such as a div you should also add keyboard support. For interactive elements like a button this is already taken care of by the browser.

If you use eslint-plugin-jsx-a11y you will see this warning when you add an onClick event:



71:13  warning  Visible, non-interactive elements with click handlers must have at least one keyboard listener jsx-a11y/click-events-have-key-events


Enter fullscreen mode Exit fullscreen mode

To get rid of this warning, my default approach is to add an additional handler for onKeyDown, filter for the enter/space key and trigger the same event as I have for onClick.

Additionally I add the role and tabIndex attributes as recommended.



function handleClick() { setWhatever(true) }

function handleKeyDown(e) {
  if (e.key === "Enter" || e.key === " ") {
    handleClick();
  }
}

return <div
  role={'button'}
  tabIndex={0}
  onClick={handleClick}
  onKeyDown={handleKeyDown}
>Click me!</div>


Enter fullscreen mode Exit fullscreen mode

In comparison to a button this adds quite a lot of code and makes simple components appear more complex than they really are:



function handleClick() { setWhatever(true) }

return <button onClick={handleClick}>Click me!</button>


Enter fullscreen mode Exit fullscreen mode

To avoid this, you can add a simple helper function that returns all required attributes, I called mine accessibleOnClick:



export function filterKeyTriggerButton(handler) {
  return e => {
    if (e.key === "Enter" || e.key === " ") {
      handler(e);
    }
  }
}

export function accessibleOnClick(handler) {
  return {
    role: 'button',
    tabIndex: tabIndex ?? 0,
    onKeyDown: filterKeyTriggerButton(handler),
    onClick: handler
  }
}


Enter fullscreen mode Exit fullscreen mode

TypeScript version:



export function filterKeyTriggerButton(
  handler: (e: React.KeyboardEvent) => void
) {
  return (e: React.KeyboardEvent) => {
    if (e.key === "Enter" || e.key === " ") {
      handler(e);
    }
  };
}

export function accessibleOnClick(
  handler: (e: React.KeyboardEvent | React.MouseEvent) => void,
  tabIndex?: number
) {
  return {
    role: "button",
    tabIndex: tabIndex ?? 0,
    onKeyDown: filterKeyTriggerButton(handler),
    onClick: handler,
  };
}


Enter fullscreen mode Exit fullscreen mode

In your JSX you can now use the spread opperator to add all attributes returned by accessibleOnClick.



function handleClick() { setWhatever(true) }

return <div
{...accessibleOnClick(handleClick)}
>Click me!</div>


Enter fullscreen mode Exit fullscreen mode

This is one of my favorite helper functions and I use it on a regular basis.

Do you have a similar/different approach for this type of click handlers? Let me know!

Top comments (0)