Skip to content

Instantly share code, notes, and snippets.

@alansouzati
Created October 29, 2016 05:23
Show Gist options
  • Save alansouzati/f13346a4ef7ba55428124d3fb2219254 to your computer and use it in GitHub Desktop.
Save alansouzati/f13346a4ef7ba55428124d3fb2219254 to your computer and use it in GitHub Desktop.
Adding accessibility to a date selector with an HTML table
<!DOCTYPE html>
<html lang='en-US'>
<head>
<meta charset='UTF-8'>
<title>Date and Time accessibility</title>
<style>
#announcer {
left: -100%;
right: 100%;
z-index: -1;
position: fixed;
}
.table {
outline: none;
}
.table--focus {
border-color: #ff4c00;
box-shadow: 0 0 1px 1px #ff4c00;
}
.table th {
padding: 10px;
}
.table tbody td .button {
padding: 10px;
cursor: pointer;
width: 100%;
outline: none;
background: 0 0;
border: none;
text-align: center;
}
.table tbody td .button:hover,
.table tbody td .button.active {
background-color: rgba(221, 221, 221, 0.5);
}
</style>
</head>
<body>
<div id='content'>
<h1>October 2016</h1>
<table id='table' tabindex='0' class='table'
aria-label='October 2016 (use arrow keys to navigate)'>
<thead>
<tr>
<th>Sun</th>
<th>Mon</th>
<th>Tue</th>
<th>Wed</th>
<th>Thu</th>
<th>Fri</th>
<th>Sat</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-09-25")'>
25
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-09-26")'>
26
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-09-27")'>
27
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-09-28")'>
28
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-09-29")'>
29
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-09-30")'>
30
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-01")'>
1
</button>
</td>
</tr>
<tr>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-02")'>
2
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-03")'>
3
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-04")'>
4
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-05")'>
5
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-06")'>
6
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-07")'>
7
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-08")'>
8
</button>
</td>
</tr>
<tr>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-09")'>
9
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-10")'>
10
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-11")'>
11
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-12")'>
12
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-13")'>
13
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-14")'>
14
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-15")'>
15
</button>
</td>
</tr>
<tr>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-16")'>
16
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-17")'>
17
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-18")'>
18
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-19")'>
19
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-20")'>
20
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-21")'>
21
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-22")'>
22
</button>
</td>
</tr>
<tr>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-23")'>
23
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-24")'>
24
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-25")'>
25
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-26")'>
26
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-27")'>
27
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-28")'>
28
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-29")'>
29
</button>
</td>
</tr>
<tr>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-30")'>
30
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-10-31")'>
31
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-11-01")'>
1
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-11-02")'>
2
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-11-03")'>
3
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-11-04")'>
4
</button>
</td>
<td>
<button class='button' tabindex='-1'
onclick='onDateConfirm(this, "2016-11-05")'>
5
</button>
</td>
</tr>
</tbody>
</table>
</div>
<div id='announcer' aria-live='assertive' />
<script type='text/javascript'>
const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const dateTable = document.getElementById('table');
const rows = dateTable.querySelectorAll('tbody tr');
let activeCell;
// utility functions
function announce (message) {
const announcer = document.getElementById('announcer');
if(announcer) {
announcer.setAttribute('aria-live', 'off');
announcer.innerHTML = message;
setTimeout(() => announcer.innerHTML = '', 500);
announcer.setAttribute('aria-live', 'assertive');
}
}
function removeActiveCell() {
dateTable.querySelectorAll('tbody .button.active').forEach(
(cell) => cell.classList.remove('active')
);
}
function addActiveCell() {
const activeCellNode = rows[activeCell[0]].columns[activeCell[1]];
activeCellNode.classList.add('active');
announce(
`${DAYS[activeCell[1]]}, ${activeCellNode.innerHTML} (press enter or space to select it)`
);
}
function onDateConfirm(event, date) {
alert(`You selected: ${date}`);
}
// add focus managements for each of the cells
rows.forEach((row, rowIndex) => {
row.columns = Array.from(row.children).map((cell, cellIndex) => {
const element = cell.querySelector('.button');
element.onfocus = () => {
removeActiveCell();
activeCell = [rowIndex, cellIndex];
addActiveCell();
};
element.onblur = () => {
removeActiveCell();
activeCell = undefined;
};
return element;
});
});
// focus management
dateTable.onmousedown = () => {
dateTable.preventFocus = true;
}
dateTable.onmouseup = () => {
dateTable.preventFocus = false;
}
dateTable.onfocus = (event) => {
if (!dateTable.preventFocus) {
dateTable.classList.add('table--focus');
}
}
dateTable.onblur = () => {
removeActiveCell();
activeCell = undefined;
dateTable.classList.remove('table--focus');
}
// navigation
function onSelectDate() {
rows[activeCell[0]].columns[activeCell[1]].click();
}
function onNextColumnDate() {
if (activeCell) {
if (activeCell[1] + 1 <= rows[activeCell[0]].columns.length - 1) {
removeActiveCell();
activeCell[1] = activeCell[1] + 1;
addActiveCell();
}
} else {
activeCell = [0, 0];
addActiveCell();
}
}
function onPreviousColumnDate() {
if (activeCell && activeCell[1] - 1 >= 0) {
removeActiveCell();
activeCell[1] = activeCell[1] - 1;
addActiveCell();
}
}
function onPreviousRowDate() {
if (activeCell && activeCell[0] - 1 >= 0) {
removeActiveCell();
activeCell[0] = activeCell[0] - 1;
addActiveCell();
}
}
function onNextRowDate() {
if (activeCell) {
if (activeCell[0] + 1 <= rows.length - 1) {
removeActiveCell();
activeCell[0] = activeCell[0] + 1;
addActiveCell();
}
} else {
activeCell = [0, 0];
addActiveCell();
}
}
const KEYS = {
enter: 13,
space: 32,
left: 37,
up: 38,
right: 39,
down: 40
}
dateTable.onkeydown = (event) => {
const key = (event.keyCode ? event.keyCode : event.which);
switch (key) {
case KEYS.enter:
case KEYS.space:
event.preventDefault();
onSelectDate();
break;
case KEYS.right:
event.preventDefault();
onNextColumnDate();
break;
case KEYS.left:
event.preventDefault();
onPreviousColumnDate();
break;
case KEYS.down:
event.preventDefault();
onNextRowDate();
break;
case KEYS.up:
event.preventDefault();
onPreviousRowDate();
break;
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment