mgifford/accessibility-skills/tables
Claude Code and agent skill by mgifford/accessibility-skills
- community
Checking that the SKILL.md file is still in the repository.
Load this skill whenever the project contains HTML data tables (<table> elements). Under no circumstances use tables for layout purposes. Absolutely always include <th> elements with appropriate scope attributes, a <caption> or aria-labelledby, and ensure complex tables have headers associated with data cells. Apply these rules to every data table without exception.
Install
npx vskill@latest install mgifford/accessibility-skills/tablesSKILL.md
Tables Accessibility Skill
Canonical source:examples/TABLES_ACCESSIBILITY_BEST_PRACTICES.mdinmgifford/ACCESSIBILITY.mdThis skill is derived from that file. When in doubt, the example is authoritative.
Apply these rules when creating or reviewing HTML data tables. Only load this skill if the project contains data tables.
Core Mandate
Use tables for information whose meaning depends on relationships between rows and columns. Mark those relationships in HTML so users can identify the table, navigate its cells, and hear the relevant headers with each value. The complexity of the markup should match the complexity of the information — simplifying or splitting a table is often better than adding increasingly complicated header associations.
Never use tables for layout. Use CSS Grid/Flexbox instead. Do not turn a reading-oriented data table into an application-style grid (role="grid") unless users genuinely need spreadsheet-like keyboard interaction.
Severity Scale (this skill)
| Level | Meaning | |
|---|---|---|
| Critical | Table data is completely uninterpretable by screen reader users | |
| Serious | Headers missing or misassociated; complex table with no navigation aid | |
| Moderate | No name for the table when multiple tables share a page; zebra stripes lost in forced-colours without fallback | |
| Minor | <thead>/<tbody> absent; obsolete summary attribute used |
Choose the Correct Structure First
- A list — items that don't depend on column relationships
- A definition list — term/description pairs
- A data table — reading and comparing row-column data
- An ARIA grid/treegrid — only for an application-style composite widget
with cell navigation or editing
- CSS Grid/Flexbox — page layout
Do not use a table merely to align form fields, cards, images, or page regions.
Critical: Every Data Table Must Have <th> with scope
A table with only <td> cells is Critical — screen readers announce raw data with no context. Use <th> for cells that label a row, column, or group — never a bolded <td> as a visual substitute.
For a small, unambiguous table with only one header row or column, browsers can sometimes infer header direction from <th> alone. Explicit scope="col"/scope="row" remains the dependable authoring convention, especially for large or similar-looking data. <thead>/<tbody>/<tfoot> support sticky headers, styling, scripting, and repeating headers in print — their presence does not by itself repair missing or incorrect header associations.
When a header row has an empty top-left corner cell (no header function), leave it as <td> — don't add filler text like "blank" just to occupy the cell.
Moderate: Captions and Table Names
<caption> is the native way to identify a table, especially with several tables on one page. WCAG does not require a caption in every case — an immediately associated heading or surrounding text can sometimes adequately identify a simple table. Treat <caption> as the preferred default, but don't force a duplicate name when another tested association already supplies one.
The caption must be a direct child of <table> (before <thead>), concise and specific. For unusual structure, a short summary can explain row/column organization without repeating every value:
The obsolete HTML summary attribute on <table> should not be used — use visible caption content or a nearby description instead.
Serious: Spanned Headers — scope="colgroup" / scope="rowgroup"
Use colgroup/rowgroup scope values only when the corresponding group actually exists in the table structure (matching <colgroup> or <tbody> row group):
For row groups spanning several rows, use scope="rowgroup" with rowspan on the group header. Test grouped headers with the project's actual AT combinations — if associations are difficult to understand, split into simpler tables rather than adding more complexity.
Serious: When to Use headers + id (Rarely)
Use only for tables so complex that scope causes headers to apply to the wrong cells:
Every ID must be unique in the document; every headers token must match a real header cell. Before reaching for `headers`/`id`, consider: splitting one table into several; transposing rows and columns; moving explanatory material outside the grid; reducing nested header levels; offering a filtered view. Even when technically correct, a table needing three or four headers read per cell may be functionally inaccessible in practice — per WebAIM, multiple levels of row/column headers are unlikely to be functionally understandable to a screen reader user. Validate headers/id associations after every schema or column change.
Serious: Empty, Missing, and Special Values
An empty cell is ambiguous — it can mean zero, not applicable, unknown, not reported, or intentionally blank. Use explicit text where the distinction matters: 0 for a measured zero; Not applicable; No data; Not reported; Suppressed for intentionally withheld values. Define shortened symbols (N/A, em dash) in the caption or a nearby note; prefer full text when space permits. Do not use to make an empty cell appear occupied. Explain estimated/rounded/preliminary/revised values — don't rely on colour or typographic style alone to identify them.
Serious: Totals, Subtotals, and Footers
Mark total labels as headers when they identify the row's values:
Do not communicate subtotals only through bold text, indentation, or background colour — include visible text ("Subtotal") and preserve header associations. If a total is calculated dynamically, expose update status appropriately and ensure the exported value uses the same calculation.
Moderate: Responsive Tables and Reflow
WCAG 1.4.10 allows two-dimensional scrolling for content that genuinely requires it, including data tables — the exception applies to the table itself, not the surrounding page.
Use a labelled region when separate discoverability genuinely helps — don't turn every table wrapper into a landmark. Use tabindex="0" to make overflow keyboard-scrollable, with a visible focus indicator.
Avoid responsive CSS that changes `table`/`thead`/`tbody`/`tr`/`th`/`td` to `display: block` — browser/AT behavior varies and duplicated data-label content can drift out of sync with the real headers. When a card presentation is genuinely better on small screens, render a real alternative list or definition-list view from the same data — expose one view at a time, preserve all values and controls, and test both presentations. Test at 200%/400% zoom, long translated strings, and narrow viewports.
Moderate: Sticky Headers and Columns
Keep the original semantic <th> elements — do not create focusable cloned headers. If a visual clone is unavoidable, hide it from AT and remove any duplicate IDs. Ensure sticky content doesn't obscure focus, headings, or the first data row. Test at high zoom and with text-spacing overrides.
Moderate: Sortable Columns
Put a native button inside each sortable column header; put aria-sort on the currently-sorted <th> only (not the button, not both):
When sorting changes: sort the complete dataset, not only the visible page, unless clearly stated otherwise; move aria-sort to the newly sorted <th>; use ascending/descending/other accurately; update the visible direction indicator without depending on colour alone; keep keyboard focus on the activated button; preserve header/data-cell associations. Do not replace the button with a click handler on <th> itself.
Moderate: Filtering, Search, and Result Counts
Update the visible result count after filtering; use role="status" when it changes without moving focus; keep active filters visible and programmatically determinable; provide a clear way to remove filters; make "No results" visible and announced. Do not announce every changed cell through a live region. Keep the caption accurate when scope/date range changes.
Moderate: Pagination and Large Datasets
Pagination is often more robust than rendering or virtualizing thousands of rows.
Identify the current page with aria-current="page"; use links for URL-based navigation, buttons for in-place state changes; preserve sort/filter parameters; state the displayed range and total ("Rows 51 to 100 of 842"); move focus only when predictable and helpful. "Load more" controls need a clear name, a result update, and predictable focus.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/tables/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/mgifford/accessibility-skills/tables)