Adding Accessibility to DataTables v2.2.0
Adding Accessibility to DataTables v2.2.0

I've been working with DataTables for a while, but I finally had my team test it for accessibility. This code overrides a ton of things, and may be able to be done in other ways. It still has a few minor bugs but mostly in place.
Here is the list of things I changed so far:
- Table headers are injected with a ton of bad aria - I removed it.
- The sort worked but it was disconnected to the label, so I created a button that reflects the sort order.
- When the results are sorted, the information was not announced to a screen reader user.
- The filter table input was put in a region to assist the screen reader user navigate and make sense of the structure.
- Set focus back to the button column header that was activated by keyboard user (doesn't work properly)
- Mouse users activating the column header button does not get the icon updated (bug)
function initializeDataTable() {
if ($.fn.DataTable.isDataTable('.table')) {
$('.table').DataTable().destroy();
}
const table = $('.table').DataTable({
ordering: true,
pageLength: 25,
destroy: true,
order: [],
headerCallback: function (thead) {
$(thead).find('th').each(function (index) {
const headerName = $(this).data('original-text');
$(this).html(`
<button class="datatable-sort btn" data-column="${index}">
${headerName} <span class="sort-icon" aria-hidden="true">↕</span>
</button>
`);
});
},
language: {
aria: {
sortAscending: '',
sortDescending: ''
}
}
});
let lastSortedButton = null;
$('.table').on('click', '.datatable-sort', function (e) {
e.preventDefault();
lastSortedButton = this; // capture button reference
const columnIdx = $(this).data('column');
const currentOrder = table.order();
let newDir = 'asc';
if (Array.isArray(currentOrder) && currentOrder.length && currentOrder[0][0] === columnIdx) {
newDir = currentOrder[0][1] === 'asc' ? 'desc' : 'asc';
}
table.order([columnIdx, newDir]).draw();
});
table.on('draw', function () {
if (lastSortedButton) {
lastSortedButton.focus();
lastSortedButton = null;
}
// Reset all arrows
$('.datatable-sort .sort-icon').text('↕');
const order = table.order();
if (Array.isArray(order) && order.length) {
const [colIdx, dir] = order[0];
const button = $(`.datatable-sort[data-column="${colIdx}"]`);
const icon = button.find('.sort-icon');
if (dir === 'asc') {
icon.text('↑');
} else if (dir === 'desc') {
icon.text('↓');
}
// Use TH's data-original-text for live announcement
const th = $(`th:eq(${colIdx})`);
const headerText = th.data('original-text');
const direction = dir === 'asc' ? 'ascending' : 'descending';
const liveRegion = $('#update-screenreader');
liveRegion.text('');
setTimeout(() => {
liveRegion.text(`Sorted by ${headerText}, ${direction}`);
}, 10);
}
});
const dataTableWrapper = $('.dt-search');
const searchLabel = dataTableWrapper.find('label');
const searchInput = searchLabel.find('input');
// Update the label text
searchLabel.contents().filter(function () {
return this.nodeType === 3; // text node
}).first().replaceWith('Filter Table Results ');
// Wrap in <section role="region">
const searchSection = $('<section role="region" aria-label="Table Filter"></section>');
dataTableWrapper.wrap(searchSection);
// Strip unnecessary ARIA on TH
$('.table thead th').removeAttr('aria-label').removeAttr('aria-sort');
}
Added CSS
table.dataTable thead .dt-column-order::before,
table.dataTable thead .dt-column-order::after {
content: none !important;
}
table.dataTable thead .dt-column-title {
display: none !important;
}
Table Head
<thead>
<tr>
<th scope="col" style="width: 180px" data-original-text="Functions">Functions</th>
<th scope="col" data-original-text="Success Criteria">Success Criteria</th>
<th scope="col" data-original-text="Description">Description</th>
<th scope="col" data-original-text="Is Issue">Is Issue</th>
<th scope="col" data-original-text="Image">ImgURL</th>
<th scope="col" data-original-text="Image Alt Text">Img Alt</th>
</tr>
</thead>
Aria Live area
<div id="criteria-feedback" class="visually-hidden" aria-live="polite"></div>
JavaScript Initialization Code
$(document).ready(function () {
initializeDataTable();
});
Replies
Hi,
Many thanks for this! As I've stated elsewhere, I'm very keen to get feedback on the accessibility aspect of DataTables. I'm not a daily driver of screen readers or accessibility tools, so the current shape of the accessibility tools in DataTables has very much been driven by community feedback.
There was a change in DataTables 2.2.2 based on this discussion which might help with what you are having a problem with. Could you check with the latest release (2.3.1) and if you are seeing a problem with the ARIA used there, let me know the details of the problem please?
I think the commit above should help with this one as well?
I did have that many years ago, and removed it based on feedback as it was too repetitive and verbose for some. Perhaps it should have a feature flag. It is a nice idea.
You can use
language.search
to set the label text for the search input. There isn't currently an option to put it in asection
though. I'd been thinking of the whole table as a section (i.e. a section of the document, rather than a section of a component). That seems to align with what MDN says about them. Possibly whatever assistive software you are using benefits from them though?That seems to work okay for me here. If I tab through the table headers, the sort icon retains focus when I press return. Is that not happening for you?
Likewise with this one. The example appears to update the icon when using the keyboard or mouse to activate sorting.
Allan