Most accessibility defects don't look serious at first.
A tester opens a ticket:
- "NVDA reads the wrong thing"
- "Focus disappears after modal close"
- "Button is not announced"
- "Keyboard navigation is inconsistent"
The first instinct is usually to patch the screen — add an aria-label, adjust tab order, maybe add a role attribute.
Sometimes that works.
But after spending years on large enterprise applications built with JSP, Spring MVC, custom tag libraries, jQuery UI dialogs, and reusable framework components, I started noticing a pattern:
Many accessibility defects were not really screen-level bugs. They were architecture problems showing up through assistive technology.
The defects that kept repeating
One thing that stood out was how often the same accessibility issues appeared across completely different screens.
Different business flows. Different developers. Different modules.
But the same problems.
That usually meant one thing: the issue existed inside a reusable component or framework pattern.
Some recurring examples:
- Modal dialogs losing focus
- Keyboard navigation escaping overlays
- Icon buttons announced incorrectly
- Custom dropdowns breaking arrow-key navigation
- Focus not returning after close
- Repeated screen reader announcements
- Visually correct controls missing semantic meaning
At a certain point, we stopped treating these as isolated defects and started treating them as reusable engineering patterns. That changed how we approached remediation entirely.
When NVDA read "shuffle" instead of the actual action
One accessibility defect looked harmless at first.
A tester reported that NVDA announced shuffle instead of Update Record.
The screen looked perfectly fine visually. The implementation looked something like this:
<button class="action-button">
<span class="material-icons">shuffle</span>
Update Record
</button>
The issue was that the Material Icon text itself became part of the accessible name calculation. To a browser, the word shuffle is still text content unless explicitly hidden from assistive technology.
The corrected implementation:
<button
type="button"
class="action-button"
aria-label="Update Record">
<span
class="material-icons"
aria-hidden="true">
shuffle
</span>
<span aria-hidden="true">
Update Record
</span>
</button>
After that, NVDA correctly announced: "Update Record button".
This was one of those defects that reminded us of something important: visual correctness and accessibility correctness are not the same thing.
When aria-label was not the real fix
Another issue involved a button that NVDA would not consistently announce.
The team tried aria-label, title, tabindex, screen-reader-only text. None of them fully solved it.
The original implementation looked roughly like this:
<app:springLink page="/submitForm">
<input
type="button"
value="Submit Form"
aria-label="Submit Form" />
</app:springLink>
The real problem was the custom framework wrapper. The abstraction around the button was interfering with native semantics and focus behavior. The control rendered visually and appeared interactive, but assistive technologies interpreted it inconsistently.
The more stable solution was simplifying back toward native HTML:
<a href="/submitForm" class="button primary">
Submit Form
</a>
This became a recurring lesson: the farther custom components drift from native HTML behavior, the harder accessibility becomes to maintain predictably.
The modal that looked fine — until you used a screen reader
Modals caused some of the most repeated accessibility defects in the system:
- Focus escaping the dialog
- Focus not returning after close
- Missing dialog labels
- Keyboard users tabbing into the page behind the modal
- Screen readers announcing stale content
- Duplicate announcements from hidden dialog instances
One particularly confusing issue involved a session timeout modal. NVDA repeatedly announced:
Refresh button
Refresh button
Refresh button
...even though visually there was only one dialog on screen.
The root cause was not the button itself. The dialog component was being recreated repeatedly without destroying previous instances. Hidden dialog structures remained in the DOM, and NVDA continued detecting them through the virtual buffer.
The original implementation:
$("#timeoutDialog").dialog({
modal: true
});
The dialog initialization ran multiple times during the session timer lifecycle. The fix involved properly destroying existing instances before recreating them:
if ($("#timeoutDialog").hasClass("ui-dialog-content")) {
$("#timeoutDialog").dialog("destroy");
}
$("#timeoutDialog").dialog({
modal: true,
title: "Session Timeout"
});
Visually, the issue was invisible. From a screen reader perspective, the experience was chaotic.
This was one of the clearest examples of why automated scans alone are not enough.
Accessibility issues that were really UX issues
Some accessibility defects were technically small but had major workflow impact.
One recurring category was focus management:
- Closing a modal and losing keyboard position
- Tab order not matching business workflow
- Focus landing on hidden elements
- Keyboard users restarting navigation from the top of the page
In transactional enterprise applications with long forms and multi-step workflows, these issues matter a lot.
One session timeout dialog had three actions: Close, Refresh Session, Return to Login. Visually, the buttons were aligned correctly. But keyboard order felt inconsistent because DOM order and intended workflow didn't match.
We ended up intentionally designing focus order around user behavior instead of visual layout alone. We also standardised focus restoration:
const triggerElement = document.activeElement;
$("#timeoutDialog").dialog({
close: function () {
triggerElement.focus();
}
});
That small change improved workflow continuity significantly for keyboard users.
Automated testing passed — but real interaction still failed
One thing we repeatedly observed:
- Axe passed ✓
- Lighthouse passed ✓
- Visual QA passed ✓
...but real screen reader interaction still felt broken.
Automated tools were excellent for missing labels, colour contrast, duplicate IDs, and missing form associations. But they couldn't fully validate announcement quality, workflow predictability, interaction consistency, keyboard experience, or screen reader context.
This became especially obvious in dynamic dialogs, custom dropdowns, reusable JSP components, timer-based UI behavior, and AJAX-rendered content.
The most difficult accessibility issues appeared where framework abstractions and dynamic rendering replaced native browser behavior.
Accessibility debt becomes framework debt
One important realization from large enterprise systems: accessibility debt accumulates very similarly to technical debt.
Over time, applications collect custom wrappers, reusable dialogs, proprietary navigation components, utility JavaScript layers, and framework abstractions.
Eventually accessibility problems stop belonging to individual screens. They become framework-level problems.
At that point, fixing defects one page at a time becomes inefficient. The better long-term solution is:
- Reusable accessibility standards
- Component-level remediation
- Documented interaction patterns
- Standardised focus behavior
- Semantic HTML guidelines
- Accessibility validation integrated into development workflow
That shift changed accessibility from a QA activity into an engineering practice.
Final thoughts
The biggest accessibility lesson I learned from enterprise systems: screen readers are often exposing deeper interaction and architecture problems that already exist in the application.
Many defects initially looked like missing labels, incorrect roles, or keyboard issues. But underneath, the real problem was usually broken component abstraction, inconsistent focus behavior, dynamic rendering side effects, or custom UI patterns replacing native browser behavior.
And in many cases, the best accessibility fix was not adding more ARIA. It was simplifying the component back toward predictable HTML semantics.
I also built PR Sentinel — an AI-powered frontend PR reviewer that catches accessibility and UI quality issues before they ship, built with Gemma 4.













