Accessibility - React Ensure click events have key events
Another approach without decorators could be to use object spread to add the necessary props to an element:
function buttonize(handlerFn) { return { role: 'button', onClick: handlerFn, onKeyDown: event => { // insert your preferred method for detecting the keypress if (event.keycode === 13) handlerFn(event); } }}
and use like so:
<div className="element-with-very-good-excuse-to-dont-be-a-button" {...buttonize(this.myAction)}>click me</div>
Finally I only see 2 solutions:
1 I can create a component that encapsulate all those actions... But this is the work of a button. And I want not to open a new way to create buttons in my project, this is just for exceptions and this can create a vicious behave inside the project. And for that we have another component... the button :)
2 create a decorator for the actions.
export function a11yButtonActionHandler(target, key, descriptor) { const fn = descriptor.value; if (typeof fn !== 'function') { throw new Error(`@a11yButtonActionHandler decorator can only be applied to methods not: ${typeof fn}`); } descriptor.value = function actionHandler(event) { if (!event || event.type === 'click' || (['keydown', 'keypress'].includes(event.type) && ['Enter', ' '].includes(event.key)) ) { fn.call(this, event); } }; return descriptor;}
and use the decorator.
@a11yButtonActionHandlermyAction(event) { ...}
<div className="element-with-very-good-excuse-to-dont-be-a-button" role="button" tabIndex="0" onKeyDown={ this.myAction } onClick={ this.myAction }>
Great solutions here already, but I wanted an option where I didn't have to use decorators or prop-spreading. So I rejigged some of the existing answers into this:
function keyDownA11y(handler) { return function onKeyDown(event) { if ( ['keydown', 'keypress'].includes(event.type) && ['Enter', ' '].includes(event.key) ) { handler(); } }}
usage:
<div onClick={myHandler} onKeyDown={keyDownA11y(myHandler)} role="button"> Hurray!</div>
EDIT: Alternatively, you could use this to make an A11yButton
component and just import that. This way, you could get the benefits of cragb's excellent buttonizer
solution:
import React from 'react';export default function A11yButton({ elementType, onClick, ...props}) { return React.createElement(elementType, { ...props, onClick, onKeyDown: keyDownA11y(onClick), role: 'button', // Add other props that might be necessary, like "tabIndex: 0," });}
Usage:
<A11yButton elementType="div" onClick={myHandler}> Hurray!</A11yButton>