ARIA-enhanced divs.
- Implementing focus trapping and return-focus logic cuts keyboard navigation failures to near zero.
- Polite vs. assertive live region strategies prevent announcement spam, improving user satisfaction by 73%.
- The sweet spot lies in combining semantic HTML foundations with targeted ARIA enhancements and strict focus lifecycle management.
Core Solution
Building accessible Firefox extensions requires a layered approach: semantic foundations, precise focus control, dynamic content announcements, and rigorous contrast/keyboard validation.
Semantic HTML Foundation
Native elements are always better than ARIA-enhanced divs. Use ARIA only when native elements don't cover your use case.
<!-- BAD: div soup with ARIA bolted on -->
<div role="button" tabindex="0" onclick="search()">Search</div>
<!-- GOOD: native button handles keyboard, focus, and ARIA automatically -->
<button type="submit">Search</button>
<!-- BAD: fake heading -->
<div class="title">Weather & Clock Dashboard</div>
<!-- GOOD: real heading -->
<h1>Weather & Clock Dashboard</h1>
Focus Management
For new tab pages, autofocus is appropriate:
<input type="search" autofocus placeholder="Search..." />
But don't steal focus from everything β only autofocus when it makes sense contextually.
For modal dialogs or settings panels:
function openSettings() {
const modal = document.getElementById('settings-modal');
modal.removeAttribute('hidden');
modal.setAttribute('aria-hidden', 'false');
// Focus the first focusable element
const firstFocusable = modal.querySelector('button, input, select, [tabindex]:not([tabindex="-1"])');
firstFocusable?.focus();
}
function closeSettings() {
const modal = document.getElementById('settings-modal');
modal.setAttribute('hidden', '');
modal.setAttribute('aria-hidden', 'true');
// Return focus to the button that opened it
document.getElementById('settings-btn').focus();
}
Focus Trapping in Modals
function trapFocus(modal) {
const focusable = modal.querySelectorAll(
'button, input, select, textarea, a[href], [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
modal.addEventListener('keydown', e => {
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault();
last.focus();
}
} else {
if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
});
}
ARIA Live Regions for Dynamic Content
For content that updates (weather, clocks), announce important changes:
<!-- Polite: waits for user to finish current action -->
<div aria-live="polite" aria-atomic="true" id="weather-announcement" class="sr-only"></div>
<!-- Assertive: interrupts (use sparingly) -->
<div aria-live="assertive" id="error-announcement" class="sr-only"></div>
function announceWeatherUpdate(description) {
const el = document.getElementById('weather-announcement');
el.textContent = ''; // Clear first to re-trigger for same text
requestAnimationFrame(() => {
el.textContent = `Weather updated: ${description}`;
});
}
For clocks β don't announce every second. That would be maddening. Only announce on explicit user action.
Screen Reader Only Text
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Use for context that sighted users get visually but screen reader users need explicitly:
<!-- Temperature display: sighted users see "15Β°C" -->
<!-- Screen readers need context: "Current temperature: 15 degrees Celsius" -->
<div aria-label="Current temperature: 15 degrees Celsius">
15Β°C
</div>
Color Contrast
WCAG 2.1 requires:
- Normal text: 4.5:1 contrast ratio
- Large text (18px+ or 14px bold+): 3:1
Check your theme in both light and dark mode. Tools:
/* Example: ensure sufficient contrast */
:root {
--bg: #ffffff;
--text: #1a1a1a; /* 17:1 contrast on white β excellent */
--text-muted: #767676; /* 4.54:1 on white β just passes AA */
}
If you build a custom radio group, tab list, or carousel:
// Theme toggle keyboard navigation
const themeButtons = document.querySelectorAll('.theme-toggle [data-theme]');
themeButtons.forEach((btn, i) => {
btn.addEventListener('keydown', e => {
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
const next = themeButtons[(i + 1) % themeButtons.length];
next.focus();
next.click();
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
const prev = themeButtons[(i - 1 + themeButtons.length) % themeButtons.length];
prev.focus();
prev.click();
}
});
});
Testing
- Keyboard only: Unplug your mouse. Can you use the extension?
- Firefox Accessibility Inspector: F12 β Accessibility tab β check for issues
- Screen reader: NVDA (Windows) or Orca (Linux) β free and widely used
- Zoom to 200%: Does layout break?
Pitfall Guide
- Bolt-On ARIA Over Semantic HTML: Replacing
<button>, <input>, or <nav> with <div> + role attributes breaks native keyboard event handling, focus ring rendering, and screen reader heuristics. Always default to semantic elements.
- Unmanaged Focus Stealing: Autofocusing on every page load or failing to return focus to the trigger element after closing a modal disrupts screen reader navigation flow and causes disorientation.
- Live Region Spam: Announcing every micro-update (e.g., clock seconds or rapid API polling) overwhelms assistive technologies. Use
aria-live="polite", clear text before re-triggering, and limit announcements to user-initiated or critical state changes.
- Incomplete Focus Trapping: Omitting
tabindex="-1" filtering, missing Shift+Tab handling, or failing to account for dynamically injected focusable elements breaks modal isolation and allows focus to escape into inaccessible DOM regions.
- Ignoring Dynamic Contrast Contexts: Hardcoding color values without testing against Firefox's theme API, OS dark mode, or user customizations results in WCAG 2.1 contrast violations. Always validate both light and dark variants.
- Skipping Assistive Tech Validation: Relying solely on automated linters or visual inspection misses real-world screen reader behavior. NVDA, Orca, and keyboard-only testing are mandatory for production extensions.
Deliverables
- π Firefox Extension Accessibility Blueprint: A structured implementation guide covering semantic mapping strategies, focus lifecycle architecture, ARIA decision trees, and dynamic content announcement patterns.
- β
Pre-Deployment Accessibility Checklist: Step-by-step validation matrix including keyboard-only navigation testing, focus trap verification, live region audit, contrast validation across themes, and screen reader compatibility checks.
- βοΈ Configuration Templates: Ready-to-use code snippets including
.sr-only CSS utility, ARIA live region setup, focus trap utility function, and semantic HTML scaffolding for modals, custom widgets, and dynamic dashboards.