DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

Accessibility in Firefox Extensions: ARIA, Focus Management, and Screen Readers

Accessibility in Firefox Extensions: ARIA, Focus Management, and Screen Readers

Browser extensions should work for everyone. Most accessibility mistakes are easy to fix once you know what to look for.

The Basics: Semantic HTML First

The most important accessibility tool is HTML semantics. Use the right element for the job:

<!-- 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>
Enter fullscreen mode Exit fullscreen mode

Native elements are always better than ARIA-enhanced divs. Use ARIA only when native elements don't cover your use case.

Focus Management

For new tab pages, autofocus is appropriate:

<input type="search" autofocus placeholder="Search..." />
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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();
      }
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
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}`;
  });
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 */
}
Enter fullscreen mode Exit fullscreen mode

Keyboard Navigation for Custom Widgets

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();
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

Testing

  1. Keyboard only: Unplug your mouse. Can you use the extension?
  2. Firefox Accessibility Inspector: F12 → Accessibility tab → check for issues
  3. Screen reader: NVDA (Windows) or Orca (Linux) — free and widely used
  4. Zoom to 200%: Does layout break?

Weather & Clock Dashboard — free Firefox new tab extension. Built with accessibility in mind: keyboard nav, ARIA labels, semantic HTML. MIT licensed.

Top comments (0)