Skip to content

Instantly share code, notes, and snippets.

@tak-dcxi
Created July 3, 2025 02:02
Show Gist options
  • Select an option

  • Save tak-dcxi/3e57bbeb3277d7d283370bc1705d4ea4 to your computer and use it in GitHub Desktop.

Select an option

Save tak-dcxi/3e57bbeb3277d7d283370bc1705d4ea4 to your computer and use it in GitHub Desktop.
ResponsiveTable
/**
* Add ARIA attributes to table elements and their children.
* When the display property is changed with CSS, the element may not be recognized as a table.
* @see https://adrianroselli.com/2018/05/functions-to-add-aria-to-tables-and-lists.html
* @param {HTMLElement} container - Container element to apply ARIA attributes to
* @returns {void}
*/
function addTableARIA(container: HTMLElement): void {
const tables = container.querySelectorAll<HTMLTableElement>("table");
for (const table of tables) {
table.setAttribute("role", "table");
const caption = table.querySelector("caption");
if (caption) {
caption.setAttribute("role", "caption");
}
const rowGroups = table.querySelectorAll<HTMLElement>(
"thead, tbody, tfoot"
);
for (const rowGroup of rowGroups) {
rowGroup.setAttribute("role", "rowgroup");
}
const rows = table.querySelectorAll<HTMLTableRowElement>("tr");
for (const row of rows) {
row.setAttribute("role", "row");
const cells = row.querySelectorAll<HTMLTableCellElement>("td");
for (const cell of cells) {
cell.setAttribute("role", "cell");
}
const headers = row.querySelectorAll<HTMLTableCellElement>("th");
for (const header of headers) {
header.setAttribute(
"role",
header.getAttribute("scope") === "row" ? "rowheader" : "columnheader"
);
}
}
}
}
/**
* Custom element to preserve table semantics
* Maintains accessibility even when display property is changed with CSS
*/
export class ResponsiveTable extends HTMLElement {
/** @private */
private observer: MutationObserver | null = null;
constructor() {
super();
}
/**
* Called when the element is added to the document
* Sets up initial ARIA attributes and mutation observer
* @returns {void}
*/
connectedCallback(): void {
// Initial ARIA attribute addition
this.applyARIA();
// MutationObserver to handle dynamically added elements
this.observer = new MutationObserver(() => {
this.applyARIA();
});
this.observer.observe(this, {
childList: true,
subtree: true
});
}
/**
* Called when the element is removed from the document
* Cleans up the mutation observer
* @returns {void}
*/
disconnectedCallback(): void {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
}
/**
* Apply ARIA attributes to table elements within this custom element
* @private
* @returns {void}
*/
private applyARIA(): void {
addTableARIA(this);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment