When to use tables
Use HTML tables for:
- Displaying tabular data with logical relationships between rows and columns
- Data that needs to be compared across categories
- Structured information where row and column headers provide context
Do not use HTML tables for:
- Page layout (use CSS Flexbox or Grid instead)
- Creating multi-column layouts
- Tables within tables also called "nested tables (external link)" are strongly discouraged
- Positioning images or text
- Only exception: HTML email templates for email client compatibility may use table layout using
role="presentation"
Historical context: In the past, tables were commonly used for page layout before CSS layout methods existed. Modern web development uses CSS for all layout purposes, reserving tables exclusively for tabular data.
Table structure and semantic markup
Required table elements
All accessible HTML tables must include:
- Caption or
aria-labelledby- Use
<caption>tag as the first child of the table element - The caption should be unique and descriptive
- Alternative: Use
aria-labelledbyto point to theidof a heading that directly precedes the table
- Use
- Table headers (
<th>)- Use
<th>for all header cells (column and/or row headers) - Use
<td>for all data cells - Headers must be descriptive and meaningful
- Use
- Scope attribute
- Use
scope="col"for column headers - Use
scope="row"for row headers - Note: While modern screen readers can sometimes infer scope without the attribute, using explicit
scopeattributes is highly recommended as a best practice
- Use
Table sectioning elements
The <thead>, <tbody>, and <tfoot> elements group table rows into logical sections:
Benefits:
- Semantic meaning: Better represents data structure for accessibility and screen readers
- Styling: Easier to apply specific CSS to different table sections
- Printing: Enables header and footer repetition across printed pages (using print media queries)
- Independent scrolling: Allows body content to scroll while keeping headers fixed (use cautiously—can cause issues at 400% zoom, on mobile, or with screen magnifiers)
Structure order:
<thead>- Contains column headers (one or more<tr>with<th>cells)<tfoot>- Contains footer rows (renders at bottom of table)<tbody>- Contains main data rows (can have multiple<tbody>elements, but only one<thead>and one<tfoot>)
These elements do not affect layout by default; their impact comes through CSS styling and browser functionality.
Handling symbols and abbreviations in headers
When using symbols (%, #, @, ., /, -) in table headers or cells, follow these patterns:
Option 1: Symbol in header only
<th>
<span aria-hidden="true">%</span>
<span class="visually-hidden">Percentage</span>
</th>Option 2: Caption provides context
<caption>Percentage of college graduates by state</caption>
<th>
College graduates
<span aria-hidden="true">%</span>
</th>In data cells:
<td>
5
<span aria-hidden="true">%</span>
<span class="visually-hidden">Percent</span>
</td>This technique relates to WCAG 2.1 Success Criterion 1.3.1: Info and Relationships.
Complex tables
For tables with multiple header levels or irregular structures, use the id and headers attributes:
<th id="name" scope="col">Name</th>
<th id="city" scope="col">City</th>
<td headers="name city">Boston</td>For more complex table patterns, see W3C WAI Tables Tutorial (external link).
WCAG Techniques:
Best practices for table size
Maximum recommended columns: 6-7 columns (including action column with buttons) - For tables requiring more columns, consider building a column selector control - Default view shows 6 columns; users can select additional columns to display
Maximum recommended rows per page: 10 rows - Implement pagination for larger datasets - Provide a dropdown to control entries per page (do not exceed 10 per page)
Interactive elements in tables
Buttons and links with unique labels
When tables contain multiple interactive elements (Edit, Delete, View buttons or links), each must have unique accessible names.
Use aria-labelledby technique:
<table>
<caption>User accounts</caption>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td id="user1">Jane Smith</td>
<td>jane@example.com</td>
<td>
<button type="button" aria-labelledby="edit user1">Edit</button>
<button type="button" aria-labelledby="delete user1">Delete</button>
</td>
</tr>
</tbody>
</table>View Responsive Table CodePen example (external link)
Checkboxes in tables
When using checkboxes for row selection (e.g., for bulk deletion): - Place checkboxes in the first column - Each checkbox must have a proper <label> element - Use aria-labelledby to create unique labels for each checkbox
Space-saving action menus
For tables with limited space, use a single "Actions" button that opens a dropdown menu:
<button type="button" aria-labelledby="actions user1">Actions</button>
<!-- Dropdown shows: Edit, View, Delete -->Important: Avoid icon-only buttons as they exclude voice/speech activation software users. Always include visible text labels or use the dropdown pattern.
Lists within table cells
You can include semantic lists within table cells:
<td>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</td>
Empty table cells
For HTML tables on web pages
Sighted users: Visual indicators are optional. Cells can appear blank or empty.
Screen reader users: Screen reader-only text is required.
Implementation options:
Option 1: Empty cell with visually hidden text
<td>
<span class="visually-hidden">data not shown</span>
</td>Option 2: Cell with visual dash symbol
<td>
<span class="visually-hidden">data not shown</span>
<span aria-hidden="true">-</span>
</td>CSS for visually hidden text:
.visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
clip-path: none;
left: -1px;
}Best practices:
- Use meaningful labels like "data not shown," "not applicable," or "no data available"
- Avoid technical abbreviations like "NA" unless explained in a table legend
- If using visual symbols, include a legend above the table explaining their meaning
- For “null” values from databases, use front-end logic to display meaningful text
- Use “0” (zero) for actual zero results: When data exists and the value is legitimately zero, display "0". Do not use "0" for null or missing data from the database.
Table legend for symbols
When using icons or symbols in tables:
- Add a legend above the table explaining what each symbol means
- Non-interactive symbols should have tooltips on focus/hover
- Voice/speech activation software users still need the legend (tooltips alone aren’t sufficient)
- Never use color alone to indicate state or meaning
- Always combine color with text, icons, or patterns
Filtering, sorting, and pagination
Filtering and sorting controls
Recommended approach: Place filter and sort controls above the table, not in table headers.
Why avoid buttons in table headers:
- Never fully accessible
- Confusing for keyboard and screen reader users
- Difficult to make clear and intuitive
Better pattern:
<form>
<div class="table-controls">
<label for="filter">Filter by:</label>
<input type="text" id="filter" />
<label for="sort">Sort by:</label>
<select id="sort">
<option value="name">Name</option>
<option value="date">Date</option>
</select>
<button type="submit">Apply filter</button>
</div>
</form>Keyword search
Use an input field with a descriptive label and a submit button:
<form>
<label for="search">Search table:</label>
<input type="text" id="search" />
<button type="submit">Search</button>
</form>External table controls
Common controls to include above or below tables:
- Filter/sort widgets
- Results count (e.g., "Showing 1-10 of 247 results")
- Pagination controls - Download data button (CSV, Excel, etc.)
- Keyboard jump links (skip to table, skip past table)
Responsive design and browser zoom
Browser zoom requirements
Tables must support browser zoom up to 400% without:
- Truncating or clipping content
- Breaking layout or functionality
Note: Data tables are an exception to the WCAG 2.1 Success Criterion 1.4.10: Reflow, which means horizontal scrolling is allowed for data tables at high zoom levels.
Text resizing: Content must remain readable when text is resized to 200%.
Responsive card layout
For mobile or narrow viewports, consider converting table rows into card layouts:
Benefits:
- Better mobile experience
- Avoids horizontal scrolling
- Easier to scan on small screens
Implementation: Use CSS media queries to transform table layout into stacked cards below a certain breakpoint.
Sticky headers and columns
Pros:
- Keep context visible while scrolling
- Helpful for large datasets
Cons:
- Can cause issues starting at 150% browser zoom and higher
- Problematic on mobile devices
- May interfere with screen magnifiers
- Particularly important to avoid when columns contain similar content (dates, times, numbers)
Recommendation: Use a CSS media query at 1024px viewport width or below to remove sticky headers and sticky columns. This prevents accessibility issues at higher zoom levels and on mobile devices.
Horizontal scrolling
Data tables are allowed to have horizontal scrolling (exception to WCAG 2.1 Success Criterion 1.4.10: Reflow). When horizontal scrolling is present:
- Ensure the scrollable area is keyboard accessible
- Provide clear visual indication that content extends off-screen
- Note that some browsers (particularly on iPad/iPhone) do not show a visible scrollbar until the user touches the screen, which can make horizontal scrolling less discoverable for touch screen and keyboard-only users
Screen reader navigation
How screen reader users navigate tables
Screen reader users have specific keyboard commands (shortcuts) to navigate tables:
- Navigate by column
- Navigate by row
- Read column headers
- Read row headers
- Jump to specific cells
This is why proper table markup is critical:
<th>elements define headers- scope attributes define header relationships
<caption>provides table context (especially important when there are multiple tables on a page)- Empty cells need screen reader text
Color and visual indicators
Do not use color alone to convey information in tables.
WCAG 2.1 Success Criterion 1.4.1: Use of Color requires that color is not the only visual means of conveying information.
Examples of violations:
- Red/green to indicate pass/fail (without text or unique icons)
- Color-coded cells without additional indicators
Accessible alternatives:
- Include text labels ("Pass"/"Fail")
- Add unique icons with appropriate alternative text for different states
- Use patterns in addition to color
- Provide a legend explaining the combination of icons, text, and color used in the table
File export accessibility
CSV files
- Empty cells are allowed
- CSV is plain text format - no accessibility steps required
- However, avoid entire rows or columns being empty (confusing for non-sighted users)
- The data must be accessible when displayed in HTML table format on your website
Excel files
- Empty cells are allowed
- Use Excel’s "Format as Table" option to define headers
- Avoid merged or split cells
- Ensure clear header row or column
- Note: Once formatted as a table, completely empty rows/columns can confuse screen reader users
Word, PowerPoint, and PDF files
- Empty cells are not allowed
- Must use placeholders ("NA", "not available", etc.)
- PDF documents require manual tagging and cannot be made accessible using backend logic alone
- Recommendation: Use Word documents instead of PDFs when possible, as they can be made accessible with backend logic
User-initiated print-to-PDF
- Print-to-PDF initiated by users is exempt from accessibility guidelines
Documents training resources
Testing accessible tables
Developer testing checklist
- Table contains only tabular data (not used for layout)
- Table has unique
<caption>oraria-labelledby - All headers use
<th>with appropriatescopeattributes - All data cells use
<td> - Interactive elements that have repeated visible text (e.g., "View", "Edit", "Delete") must have unique accessible names using
aria-labelledby - Empty cells include screen reader text
- Color is not the only indicator of meaning
- Table instructions/legend provided when needed
- No merged cells (except where semantically appropriate)
- No nested tables (except email templates)
Browser zoom testing
Test at browser zoom levels from 100% to 400%:
- At all zoom levels (100%, 150%, 200%, 300%, 400%), text remains readable
- At all zoom levels, content is not truncated or clipped
- Content adapts appropriately (card layout or horizontal scroll for data tables)
Mobile testing
- Test in mobile viewport (320px - 428px wide)
- Verify card layout or appropriate responsive behavior
- Touch targets are at least 44x44 pixels
Keyboard testing
- All interactive elements are keyboard accessible
- Focus indicators are visible
- Tab order is logical
- Filter/sort controls work with keyboard only
Screen reader testing
Note: Screen reader testing should only be performed by testers who have been properly trained in screen reader usage. For testers who are not trained in screen readers, use the ANDI accessibility tool to verify proper table structure and the browser developer tools to verify unique accessible names for repeated elements.
For trained screen reader testers using NVDA or JAWS (Windows) or VoiceOver (Mac):
- Table is announced as a table
- Caption/label is read
- Column headers are announced when navigating cells
- Row headers are announced (if applicable)
- Interactive elements have unique names
- Empty cells have meaningful labels
- Table navigation commands work correctly
ANDI testing
Use the ANDI accessibility testing tool:
- Run ANDI on the page containing the table
- Review structure mode to verify proper table markup
- Check that all headers are properly identified
- Verify accessible names for interactive elements
Related WCAG success criteria
- 1.3.1: Info and Relationships (Level A) - Table structure must be programmatically determinable
- 1.3.2: Meaningful Sequence (Level A) - Reading order must be logical
- 1.4.1: Use of Color (Level A) - Color alone must not convey information
- 2.1.1: Keyboard (Level A) - All functionality available via keyboard
- 2.4.6: Headings and Labels (Level AA) - Headers must be descriptive
- 2.5.8: Target Size (Minimum) (Level AA) - Interactive elements minimum 24x24px (recommend 44x44px)
- 4.1.2: Name, Role, Value (Level A) - Interactive elements must have accessible names
Additional Resources
For help including design reviews, code reviews, or additional training contact the ACCESS team using the ACCESS team’s intake form.