Building the common stuff right
Accordion/Collapse
<div class="accordion">
<h3>
<button aria-expanded="false" aria-controls="panel1">
Section 1
</button>
</h3>
<div id="panel1" hidden>
<p>Content goes here</p>
</div>
</div>
button.addEventListener('click', () => {
const expanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', !expanded);
panel.hidden = expanded;
});
Tabs That Actually Work
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel1">
Tab 1
</button>
<button role="tab" aria-selected="false" aria-controls="panel2">
Tab 2
</button>
</div>
<div role="tabpanel" id="panel1">Content 1</div>
<div role="tabpanel" id="panel2" hidden>Content 2</div>
Keyboard support required:
- Arrow keys to move between tabs
- Home/End for first/last tab
- Tab key moves into panel content
Dropdown Menus
<nav>
<button aria-expanded="false" aria-haspopup="menu">
Menu
</button>
<ul role="menu" hidden>
<li role="menuitem"><a href="/home">Home</a></li>
<li role="menuitem"><a href="/about">About</a></li>
</ul>
</nav>
Remember: Escape should close, Arrow keys should navigate.
Data Tables That Make Sense
<table>
<caption>Q3 2024 Sales Report</caption>
<thead>
<tr>
<th scope="col">Product</th>
<th scope="col">Units Sold</th>
<th scope="col">Revenue</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Widgets</th>
<td>1,234</td>
<td>$12,340</td>
</tr>
</tbody>
</table>
Tables need:
- <caption> or aria-label for context
- <th> elements for headers
- scope attributes for relationships
- Logical reading order
→ Component patterns that work:
- Inclusive Components – Heydon Pickering’s brilliant patterns
- ARIA Patterns Library – The official component patterns