Reputation: 5235
So I have a few div
s one below the other. I want them to be "web accessible" via arrows keys, i.e, if I press the down key next div
should be highlighted and if I press the up key, the previous one should be highlighted.
If I had a button I could have used the tabIndex
property to achieve it.
The thing is div
s are not actionable items. So it could be confusing for the users if I make them somehow accessible through tabs. Hence, I want it only through arrow keys of the keyboard.
I'm even willing to convert it into <ul><li><li></ul>
if the desired behavior could be achieved.
<div>Div 1</div>
<div>Div 2</div>
<div>Div 3</div>
<div>Div 4</div>
This is what I tried with <li>
s. But If I click on the first li
I cant go and highlight the next one by pressing the down arrow.
<ul role="menu" aria-label="menu">
<li role="menuitem" tabindex="0">list 1</li>
<li role="menuitem" tabindex="0">list 2</li>
<li role="menuitem" tabindex="0">list 3</li>
<li role="menuitem" tabindex="0">list 4</li>
<li role="menuitem" tabindex="0">list 5</li>
</ul>
Upvotes: 5
Views: 8160
Reputation: 129
There is an ARIA widget that allows for keyboard navigation via arrow keys, with one tab stop instead of many: your list of similar <div>
s can be formulated as a single-column Grid.
Initially, the cell in the first row gets tabindex="0"
and all others get tabindex="-1"
.
Using JavaScript, arrowing down focuses the cell in the second row. It gets tabindex="0"
while the cell in the first row gets tabindex="-1"
, so that there is only ever one cell that is tabbable/focusable. The full keyboard handling is specified in the examples/docs.
In JavaScript, you will need to keep track of currently focused item.
Here's how I would set it up using React:
class MyGrid extends React.Component {
constructor(props) {
super(props)
this.state = {
focusedItemIndex: 0
}
this.cellRefs = []
this.handleFocus = (index => {
this.setState(() => ({
focusedItemIndex: index
}))
}).bind(this)
this.handleKeydown = ((index, {
key
}) => {
switch (key) {
case 'ArrowUp': {
this.setState(() => ({
focusedItemIndex: Math.max(0, index - 1)
}))
}
break
case 'ArrowDown': {
this.setState(() => ({
focusedItemIndex: Math.min(index + 1, this.props.items.length - 1)
}))
}
break
case 'Home': {
this.setState(() => ({
focusedItemIndex: 0
}))
}
break
case 'End': {
this.setState(() => ({
focusedItemIndex: this.props.items.length - 1
}))
}
break
default: {
return
}
}
}).bind(this)
}
componentDidUpdate(_, prevState) {
if (prevState.focusedItemIndex !== this.state.focusedItemIndex) {
this.cellRefs[this.state.focusedItemIndex].focus()
}
}
render() {
return (
<div
role="grid"
aria-label="This needs a label">
<div role="row">
{this.props.items.map((item, index) => (
<div
role="cell"
key={item}
ref={el => this.cellRefs[index] = el}
tabIndex={this.state.focusedItemIndex === index ? '0' : '-1'}
onFocus={this.handleFocus.bind(this, index)}
onKeyDown={this.handleKeydown.bind(this, index)}>
{item}
</div>
))}
</div>
</div>
)
}
}
ReactDOM.render(
<MyGrid items={['A', 'B', 'C']} />,
document.querySelector('body')
)
Upvotes: 3
Reputation: 304
Hi you should use jquery for what you want. Your last source will be this:
$(function(){
document.onkeydown = function(e) {
var selected = $('.mnu-item:focus').index();
var next = selected + 1;
var pre = selected - 1;
if(pre < 0)
pre = 0;
if(next > $('.mnu-item').length)
next = $('.mnu-item').length;
if(e.which == 38) { // up
$('.mnu-item').eq(pre).focus();
}
if(e.which == 40) { // down
$('.mnu-item').eq(next).focus();
}
e.preventDefault(); // prevent the default action (scroll / move caret)
};
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul role="menu" aria-label="menu">
<li role="menuitem" tabindex="0" class="mnu-item">list 1</li>
<li role="menuitem" tabindex="0" class="mnu-item">list 2</li>
<li role="menuitem" tabindex="0" class="mnu-item">list 3</li>
<li role="menuitem" tabindex="0" class="mnu-item">list 4</li>
<li role="menuitem" tabindex="0" class="mnu-item">list 5</li>
</ul>
I hope this will be useful.
Upvotes: 0