JSFiddle - React, Tailwind, and code Playground
HTML
<p>
A grid implemented as described at <a href="https://www.w3.org/TR/wai-aria-practices-1.1/#grid">https://www.w3.org/TR/wai-aria-practices-1.1/#grid</a> (arrow keyboard keys move focus between grid cells, when a cell contains only a single focusable widget focus is set to a cell content).
</p>
<p>
A special style is applied to always show the focus ring for focused elements inside of the grid:
</p>
<pre><code>
[role="grid"] *:focus {
outline-style: auto;
}
</code></pre>
<ul>
<li>1. Use mouse to click on the cell of the table</li>
<li>2. Use keyboard arrow keys to move focus between checkboxes/cells/buttons within the table</li>
<li>3. Focus outline is not visible in Chrome on Windows 10</li>
<li>4. Focus outline is not the same as the default focus outline in Firefox on Windows 10</li>
</ul>
<table role="grid" aria-readonly="true" aria-multiselectable="true">
<thead>
<th><input type="checkbox" tabindex="0" title="Select all"/></th>
<th tabindex="-1">Item</th>
<th tabindex="-1">Property1</th>
<th tabindex="-1">Property2</th>
<th tabindex="-1"></th>
</thead>
<tbody>
<tr aria-selected="false">
<td><input type="checkbox" tabindex="-1" /></td>
<td tabindex="-1">item A</td>
<td tabindex="-1">10%</td>
<td tabindex="-1">11%</td>
<td><button tabindex="-1">B</button></td>
</tr>
<tr aria-selected="false">
<td><input type="checkbox" tabindex="-1" /></td>
<td tabindex="-1">item B</td>
<td tabindex="-1">10%</td>
<td tabindex="-1">11%</td>
<td><button tabindex="-1">B</button></td>
</tr>
<tr aria-selected="false">
<td><input type="checkbox" tabindex="-1" /></td>
<td tabindex="-1">item C</td>
<td tabindex="-1">10%</td>
<td tabindex="-1">11%</td>
<td><button tabindex="-1">B</button></td>
</tr>
</tbody>
</table>
The checkbox below should always show the focus ring:
<input type="checkbox" class="outline" />
CSS
[role="grid"] *:focus {
outline-style: auto;
}
:focus {
color: red;
}
.outline {
outline-style: auto;
}
JavaScript
var grid = document.querySelector('table[role="grid"]');
grid.addEventListener('focus', function (event) {
grid.querySelector('[tabindex="0"]').tabIndex = -1;
event.target.tabIndex = 0;
});
grid.addEventListener("keydown", function (event) {
var DOM_VK_UP = 38;
var DOM_VK_DOWN = 40;
var DOM_VK_LEFT = 37;
var DOM_VK_RIGHT = 39;
if (!event.altKey && !event.shiftKey && !event.ctrlKey && !event.metaKey) {
var keyCode = event.keyCode;
if (keyCode === DOM_VK_LEFT || keyCode === DOM_VK_RIGHT || keyCode === DOM_VK_UP || keyCode === DOM_VK_DOWN) {
event.preventDefault();
var cell = event.target.closest('th, td');
var row = cell.parentNode;
var rows = row.parentNode.parentNode.rows;
var rowIndex = row.rowIndex + (keyCode === DOM_VK_UP ? -1 : (keyCode === DOM_VK_DOWN ? +1 : 0));
var cellIndex = cell.cellIndex + (keyCode === DOM_VK_LEFT ? -1 : (keyCode === DOM_VK_RIGHT ? +1 : 0));
rowIndex = Math.min(Math.max(rowIndex, 0), rows.length - 1);
cellIndex = Math.min(Math.max(cellIndex, 0), rows[rowIndex].cells.length - 1);
(rows[rowIndex].cells[cellIndex].querySelector('[tabindex]') || rows[rowIndex].cells[cellIndex]).focus();
}
}
});