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.
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.
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.
A quick primer
To understand how the widget works, you need to know the two main ways accessibility gets implemented on a website:
- Source-code accessibility — writing the code in an accessible way from the start.
- 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.
<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>
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.
<script>
window.addEventListener('load', () => {
const button = document.querySelector('#myButton');
if (button) {
button.setAttribute('role', 'button');
button.setAttribute(
'aria-label',
'Submit form'
);
}
});
</script>
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.
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
});
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.
<!-- 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>
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.