Accessibility - React Ensure click events have key events

I want to be sure that all my onClick event are next to a onKeyDown event.

I will use eslint-plugin-jsx-a11y to ensure this. But in code, It is a way to do this generic. I mean, it will be annoying to do all the time:

 if(event.keyCode === 13){ ... }

I would like to have a way to tell an element that in onKeyDown in case that the user use the execute the function in the onClick. Or a similar solution like

In angular for instance, I have this clear. Let's go for a directive to do this automatically. But in React I don't know which is the best approach.


eslint rule:

3

5 Answers

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>
0

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.

@a11yButtonActionHandler
myAction(event) { ...
}

<div className="element-with-very-good-excuse-to-dont-be-a-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)}
> 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>
1

To simplify Raúl Martíns answer and don't use decorators which are still just a proposal you can also create a simple function out of it:

function isButtonAction(event) { return ( event.type === 'click' || (['keydown', 'keypress'].includes(event.type) && ['Enter', ' '].includes(event.key)) );
}

then simply check it in your action handler:

function myAction(event) { if (isButtonAction(event)) { ... }
}

I suggest you use a state to handle it. For example:

First, initial a state in your component and set the event listener:

getInitialState(){ window.addEventListener("keypress", this.onKeypresshandler);// if you are not rendering on server side, you cannot bind the listener to the element you want, which doesn't exist yet. return{ isPressed: false }
}

Then, change the state when the key keypress event is called:

onKeypresskhandler(e){ if(e.keyCode === 13){// if the key is enter this.setState({isPressed:true}) }
}

finally, check the state before executing your function

YourFunction(){ if(this.state.isPressed){// check if the key is pressed ... // the code you want to execute }
}

It is the simplest way I know. I hope it helps.

1

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like