aria-colcount
Defines the total number of columns in a table, grid, or treegrid when not all columns are present in the DOM.
Overview
The aria-colcount attribute defines the total number of columns in a table, grid, or treegrid. It's essential when implementing virtual scrolling or when only a subset of columns are rendered in the DOM.
When all columns are present in the DOM, browsers can calculate the column count automatically. However, for performance optimization with large datasets, you may only render visible columns. In these cases, aria-colcount tells assistive technologies the true total.
Use aria-colcount="-1" when the total number of columns is unknown (e.g., dynamically loaded data).
Live Demo: Virtual Table with Column Visibility
Toggle columns to show/hide (total: 8 columns):
| ID(col 1) | Name(col 2) | Email(col 3) | Phone(col 4) |
|---|---|---|---|
| 1 | Alice Johnson | alice@example.com | 555-0101 |
| 2 | Bob Smith | bob@example.com | 555-0102 |
| 3 | Carol Williams | carol@example.com | 555-0103 |
aria-colcount="8" tells screen readers there are 8 total columns, even though only 4 are currently visible. Each cell uses aria-colindex to indicate its position.
Code Examples
Basic Usage
<!-- aria-colcount indicates total columns when not all are in DOM -->
<!-- Table with 8 total columns, only 4 visible -->
<table role="grid" aria-colcount="8" aria-label="Employee directory">
<thead>
<tr>
<th aria-colindex="1">ID</th>
<th aria-colindex="2">Name</th>
<th aria-colindex="3">Email</th>
<th aria-colindex="5">Department</th>
<!-- Columns 4, 6, 7, 8 not rendered but exist -->
</tr>
</thead>
<tbody>
<tr>
<td aria-colindex="1">1</td>
<td aria-colindex="2">Alice Johnson</td>
<td aria-colindex="3">alice@example.com</td>
<td aria-colindex="5">Engineering</td>
</tr>
</tbody>
</table>
<!-- Screen reader announces: "Table with 8 columns" -->Virtual Scrolling Grid
<!-- Virtual scrolling grid with many columns -->
<div
role="grid"
aria-colcount="100"
aria-rowcount="1000"
aria-label="Spreadsheet"
>
<!-- Only render visible viewport -->
<div role="row" aria-rowindex="1">
<div role="columnheader" aria-colindex="1">A</div>
<div role="columnheader" aria-colindex="2">B</div>
<div role="columnheader" aria-colindex="3">C</div>
<!-- ... columns D through CV not in DOM ... -->
</div>
<div role="row" aria-rowindex="5">
<div role="gridcell" aria-colindex="1">Data A5</div>
<div role="gridcell" aria-colindex="2">Data B5</div>
<div role="gridcell" aria-colindex="3">Data C5</div>
</div>
</div>
<!-- aria-colcount="100" tells AT there are 100 columns total -->Unknown Column Count
<!-- When total column count is unknown -->
<!-- Use aria-colcount="-1" for dynamic/unknown totals -->
<table role="grid" aria-colcount="-1" aria-label="Dynamic data">
<thead>
<tr>
<th aria-colindex="1">Column 1</th>
<th aria-colindex="2">Column 2</th>
<!-- More columns load dynamically -->
</tr>
</thead>
</table>
<!-- -1 indicates "count unknown" to assistive technology -->React Component
// React virtual table with aria-colcount
import { useState, useMemo } from 'react';
interface Column {
id: string;
label: string;
width: number;
}
interface VirtualTableProps {
columns: Column[];
data: Record<string, string>[];
visibleColumnIndices: number[];
label: string;
}
function VirtualTable({
columns,
data,
visibleColumnIndices,
label
}: VirtualTableProps) {
const totalColumns = columns.length;
const visibleColumns = useMemo(() =>
visibleColumnIndices.map(i => ({
...columns[i],
originalIndex: i + 1 // 1-based for aria-colindex
})),
[columns, visibleColumnIndices]
);
return (
<table
role="grid"
aria-colcount={totalColumns}
aria-label={label}
>
<thead>
<tr>
{visibleColumns.map(col => (
<th
key={col.id}
aria-colindex={col.originalIndex}
scope="col"
>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, rowIndex) => (
<tr key={rowIndex}>
{visibleColumns.map(col => (
<td
key={col.id}
aria-colindex={col.originalIndex}
>
{row[col.id]}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
// Usage
function App() {
const columns = [
{ id: 'id', label: 'ID', width: 50 },
{ id: 'name', label: 'Name', width: 150 },
{ id: 'email', label: 'Email', width: 200 },
{ id: 'phone', label: 'Phone', width: 120 },
{ id: 'dept', label: 'Department', width: 120 },
];
const [visibleCols, setVisibleCols] = useState([0, 1, 2]);
return (
<VirtualTable
columns={columns}
data={data}
visibleColumnIndices={visibleCols}
label="Employee directory"
/>
);
}Best Practices
Do
- • Use when columns are virtualized or hidden
- • Always pair with aria-colindex on cells
- • Use -1 when count is truly unknown
- • Update count when columns are added/removed
- • Apply to table, grid, or treegrid elements
Don't
- • Use when all columns are in the DOM
- • Forget aria-colindex on individual cells
- • Use 0 for unknown (use -1 instead)
- • Apply to non-table/grid elements
- • Mismatch count with actual total columns

