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
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>
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>
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
}
}
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,
};
}
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>
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)