Skip to main content

How can a widget deliver accessibility?

A straightforward technical breakdown of our approach: how JavaScript can turn a non-compliant website into a fully compliant one — without changing a single line of code in your original site.

Two approaches to web accessibility

There are two main ways to make a website accessible. Each approach has its own tradeoffs.

Approach 1: Source-code remediation

A developer dives into your site's source code and fixes every issue manually: adding alt tags to images, fixing heading structure, adding form labels, and improving color contrast.

Thorough and precise fix
Requires access to source code
Expensive and slow — needs a developer for every change
Content updates break accessibility

Approach 2: JavaScript widget (post-load)

A script that loads after the page, scans every element, and dynamically adds the missing accessibility attributes. It works as a layer on top of your existing site.

No source-code access required
Updates automatically with content changes
Fast to install — a single line of code
Built-in continuous monitoring

Code examples: what it actually looks like

Four examples that show exactly how JavaScript handles accessibility — from a basic fix all the way to dynamic content.

Background

A quick primer

To understand how the widget works, you need to know the two main ways accessibility gets implemented on a website:

  1. Source-code accessibility — writing the code in an accessible way from the start.
  2. Post-load accessibility — using a script that makes the adjustments after all the content loads.

From the user's perspective — especially someone with a disability — there's no difference between the two approaches. Even when accessibility is built into the source code, there are still situations that require real-time changes via JavaScript. For example: a button that shows or hides information has to tell the screen reader whether the information is currently visible.

source-code accessibility
<button aria-expanded="false"
        aria-controls="infoPanel"
        onclick="togglePanel(this)">
  Show more info
</button>

<div id="infoPanel" hidden>
  <p>Important info for the visitor...</p>
</div>

<script>
function togglePanel(btn) {
  const panel = document.getElementById('infoPanel');
  const isExpanded = btn.getAttribute('aria-expanded') === 'true';
  btn.setAttribute('aria-expanded', !isExpanded);
  panel.hidden = isExpanded;
}
</script>
Explanation: the button has accessibility attributes (aria-expanded, aria-controls) and a JS function that updates screen readers in real time.
How the widget works

So how does our widget actually work?

The widget is a JavaScript file that runs only after the page's content has loaded. It uses a custom configuration file that specifies exactly which adjustments are needed and on which elements.

The adjustments include adding ARIA attributes, screen-reader descriptions, improved focus, keyboard navigation, and more — tailored to each site. Once the widget starts running, it iterates over the configuration and applies the required function to every element.

widget in action
<script>
window.addEventListener('load', () => {
  const button = document.querySelector('#myButton');
  if (button) {
    button.setAttribute('role', 'button');
    button.setAttribute(
      'aria-label',
      'Submit form'
    );
  }
});
</script>
Explanation: once the page has fully loaded, the widget finds the relevant element and adds the accessibility attributes from the config file.
Dynamic content

What if content loads after the widget finishes running?

This is where dynamic content comes in — content that's streamed into the page after it has loaded (for example, via AJAX or an API).

Our widget's core uses JavaScript's capabilities to perform "real-time monitoring" — it detects when new content is added to the page and then applies accessibility adjustments to it, exactly as it would for regular content.

MutationObserver
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    mutation.addedNodes.forEach((node) => {
      if (node.nodeType === 1 &&
          node.matches('.dynamic-content')) {
        node.setAttribute('tabindex', '0');
        node.setAttribute('aria-live', 'polite');
      }
    });
  });
});

observer.observe(document.body, {
  childList: true,
  subtree: true
});
Explanation: as soon as new content is added to the page (e.g. via AJAX), the widget detects it and applies the relevant accessibility adjustments.
Limitations

Are there any limitations?

Yes — one important limitation: when accessibility is built directly into the source code, the content arrives accessible straight from the server.

But when accessibility is delivered via JS, we have to wait for the content to load from the server, for the browser to finish processing it, and only then can we go over it and apply the adjustments. It doesn't prevent accessibility, but it adds an intermediate processing step that has to be accounted for.

server vs JS
<!-- content delivered accessible from the server -->
<img src="product.jpg"
     alt="photo of a new product" />

<!-- content streamed into the page dynamically -->
<script>
setTimeout(() => {
  const img = document.createElement('img');
  img.src = 'product.jpg';
  document.body.appendChild(img);

  // add the alt text only after loading
  setTimeout(() => {
    img.alt = 'photo of a new product';
  }, 100);
}, 1000);
</script>
Explanation: when an element loads late, the widget has to wait for it before making it accessible — so there's a delay between when it appears and when the adjustments are applied.

How the widget actually works

What happens behind the scenes

The UA widget loads after the browser finishes rendering the page (DOMContentLoaded). Once active, it scans the DOM — the page's element tree — and performs a series of fixes:

  • Adds ARIA attributes (role, aria-label, aria-describedby) to elements that are missing them
  • Establishes proper semantic structure — headings, lists, and landmarks
  • Handles keyboard navigation — tab order, visible focus, and keyboard shortcuts
  • Shows an accessibility menu with personalized tools for each visitor

Zero performance hit

The widget loads asynchronously and weighs less than 50KB (gzipped). It doesn't block initial page render and doesn't hurt your Core Web Vitals. The script runs only after the page is ready — so visitors never feel any delay.

Updates alongside your site

Unlike source-code fixes, the widget rescans the page on every load. If you've added new content, swapped an image, or tweaked a form — the widget detects the change and makes it accessible automatically.

Our approach: smart hybrid

At UA we don't rely on automation alone. Our widget is hand-written and hand-maintained for every site — custom code that understands your site's specific structure, paired with an automated engine that handles dynamic content.

Want to see the technology in action?

Run a free scan on your site and get a detailed report on its accessibility status.

Ready to make your site accessible?

Leave your details and we'll get back to you with a custom offer — no commitment