Please fix the errors below.
General settings content.
```
```js
tablist.addEventListener('keydown', (e) => {
const tabs = [...tablist.querySelectorAll('[role="tab"]')];
const current = tabs.indexOf(document.activeElement);
let next;
if (e.key === 'ArrowRight') next = (current + 1) % tabs.length;
else if (e.key === 'ArrowLeft') next = (current - 1 + tabs.length) % tabs.length;
else if (e.key === 'Home') next = 0;
else if (e.key === 'End') next = tabs.length - 1;
else return;
e.preventDefault();
tabs[current].setAttribute('tabindex', '-1');
tabs[current].setAttribute('aria-selected', 'false');
tabs[next].setAttribute('tabindex', '0');
tabs[next].setAttribute('aria-selected', 'true');
tabs[next].focus();
tabs.forEach((tab, i) => {
document.getElementById(tab.getAttribute('aria-controls')).hidden = (i !== next);
});
});
```
### ARIA Attributes Reference
| Attribute | When to use | Example |
|---|---|---|
| `aria-expanded` | Toggleable content (dropdowns, accordions, menus) | `3 results found.
` |
| `aria-live="assertive"` | Urgent announcements | `Session expired.
` |
| `aria-label` | Elements without visible text | `` |
| `aria-describedby` | Additional description | `
` |
### Live Regions for Dynamic Content
```html
Your session has expired. Please log in again.
```
---
## Custom Widget Accessibility Patterns
When building custom interactive widgets beyond ply's built-in components, follow these patterns for WCAG 2.1 AA compliance.
### Drag-and-Drop with Full Keyboard Support
Drag-and-drop interfaces must be fully keyboard operable (WCAG 2.1.1). Use a listbox pattern with grab/move/drop states and an `aria-live` region for screen reader announcements.
```html
Keyboard: Tab to list, Space to grab, Up/Down to move, Enter to drop, Escape to cancel.
- Review pull request
- Update documentation
- Fix navigation bug
```
```js
const list = document.getElementById('sortable-list');
const status = document.getElementById('dnd-status');
let grabbed = null;
list.addEventListener('keydown', (e) => {
const item = e.target;
if (item.getAttribute('role') !== 'option') return;
switch (e.key) {
case ' ':
e.preventDefault();
if (!grabbed) {
grabbed = item;
item.setAttribute('aria-grabbed', 'true');
status.textContent = `Grabbed ${item.textContent}. Use arrow keys to move, Enter to drop, Escape to cancel.`;
}
break;
case 'ArrowDown':
e.preventDefault();
if (grabbed) {
const next = grabbed.nextElementSibling;
if (next) {
list.insertBefore(next, grabbed);
status.textContent = `Moved down to position ${[...list.children].indexOf(grabbed) + 1}.`;
}
}
break;
case 'ArrowUp':
e.preventDefault();
if (grabbed) {
const prev = grabbed.previousElementSibling;
if (prev) {
list.insertBefore(grabbed, prev);
status.textContent = `Moved up to position ${[...list.children].indexOf(grabbed) + 1}.`;
}
}
break;
case 'Enter':
if (grabbed) {
e.preventDefault();
grabbed.setAttribute('aria-grabbed', 'false');
status.textContent = `Dropped ${grabbed.textContent} at position ${[...list.children].indexOf(grabbed) + 1}.`;
grabbed = null;
}
break;
case 'Escape':
if (grabbed) {
e.preventDefault();
grabbed.setAttribute('aria-grabbed', 'false');
status.textContent = 'Reorder cancelled.';
grabbed = null;
}
break;
}
});
```
**Accessibility features in this pattern:**
- `aria-grabbed` toggles between "true" (grabbed) and "false" (released) — announced by screen readers (WCAG 4.1.2)
- `aria-live="assertive"` region announces every move to screen readers
- Roving tabindex — only the focused item has `tabindex="0"`
- `role="listbox"` + `role="option"` — correct semantics for screen readers
- Visible keyboard instructions for sighted users
- `prefers-reduced-motion: reduce` — disable drag animations in CSS
- Full keyboard alternative: Space=grab, Arrows=move, Enter=drop, Escape=cancel (WCAG 2.1.1)
### Focus Trap for Modals
Prefer native `