JavaScript / Advanced: scheduling and trustworthy interfaces / Lesson 16
Accessible JavaScript interactions
Preserve native semantics while updating state dynamically. Connect labels, keyboard behavior, focus, errors, and live feedback to complete user tasks rather than a passing automated scan.
On this page
Lesson goals
- Choose native controls and meaningful names.
- Coordinate focus and status during state changes.
- Test keyboard and assistive-technology task completion.
Behavior starts with the right element
A button performs an action and a link navigates to a resource. Native elements already provide important keyboard and accessibility semantics; rebuilding them with a clickable div creates additional obligations. JavaScript should enhance that foundation rather than suppressing default behavior reflexively. Give controls a visible, meaningful name and ensure any accessible-name override includes the visible label so speech-input users can refer to it. A disabled state needs a reason and a recovery path where appropriate. Do not make an unavailable control the only place explaining what the user must do next.
Announce a change without stealing focus
An enrollment count can update in a status region while focus remains on the button that initiated it. Set the region up before changing its text and choose a concise message meaningful in context. Live regions help expose updates but do not guarantee identical announcements across every assistive-technology combination. The example constructs a detached component and verifies its text result; it does not claim a screen reader announced it. Real evaluation must exercise the mounted component using supported technologies and ensure repeated actions do not create noisy duplicate messages.
const root = document.createElement("section");
const button = document.createElement("button");
button.type = "button"; button.textContent = "Enroll";
const status = document.createElement("p");
status.setAttribute("role", "status");
button.addEventListener("click", () => { status.textContent = "1 enrolled"; });
root.append(button, status);
button.click();
console.log(status.textContent);
console.log(button.tagName, status.getAttribute("role"));
1 enrolled
BUTTON status
The check establishes DOM state only. Keyboard operation, focus, and actual announcement need a mounted user-task evaluation.
Focus follows the task lifecycle
When a dialog opens, users need a meaningful focus destination, usable controls, a supported dismissal path, and a sensible focus return when it closes. Prefer the native dialog element where its behavior fits, then verify the complete interaction rather than assuming a role attribute implements it. Do not move focus for every status update or rerender the focused control unnecessarily. A removed focused node can strand a keyboard user. Preserve stable identities or choose a deliberate next focus target after deletion. Test opening, errors, correction, closing, and repeated opening as one connected task.
Associate errors with the field and preserve work
Validation should identify the field, explain the problem in text, and make correction possible without discarding unrelated valid entries. aria-invalid marks the invalid state; an associated description can provide the explanation. Color alone is insufficient, and an alert should not repeatedly announce unchanged errors on every keystroke. The example demonstrates a browser DOM association and its stored attributes. It does not replace native form constraints or server validation, and the exact moment of announcing an error should follow the task rather than a blanket rule that every character requires immediate criticism.
const field = document.createElement("input");
field.id = "email"; field.type = "email";
const label = document.createElement("label");
label.htmlFor = field.id; label.textContent = "Email";
const error = document.createElement("p");
error.id = "email-error"; error.textContent = "Enter a valid email address.";
field.setAttribute("aria-invalid", "true");
field.setAttribute("aria-describedby", error.id);
console.log(label.htmlFor, field.getAttribute("aria-invalid"));
console.log(field.getAttribute("aria-describedby"));
email true
email-error
The field has an explicit label target and description reference. A real mounted form must also verify those referenced nodes and correction behavior.
Evaluate complete processes and limitations
WCAG provides testable success criteria, while manual task evaluation supplies context that an automated tool cannot infer. Combine keyboard-only use, supported screen readers, enlargement/reflow, contrast checks, and automated structural checks. Inspect dynamic states such as loading, failure, and empty results, not only the initial page. Record tested pages, process steps, technologies, and unresolved issues. A zero-error scan does not establish conformance or meet every user need. Include people with disabilities where feasible and use their findings to improve the actual task, without assuming one participant represents all experiences.
Common errors and fixes
A custom div acts like an incomplete button
- Why it happens
- Click handling replaces native keyboard semantics.
- How to fix it
- Use a native button with the intended type and label.
- Prevent it next time
- Test the same task with keyboard only.
Every update moves focus unexpectedly
- Why it happens
- Announcements and focus management are conflated.
- How to fix it
- Use an appropriate status region and move focus only when the task needs it.
- Prevent it next time
- Review focus before and after each dynamic state change.
A clean scan is claimed as universal accessibility
- Why it happens
- Detectable structural rules are confused with complete task usability.
- How to fix it
- Add manual task and assistive-technology checks with explicit scope.
- Prevent it next time
- Report criteria, methods, and unresolved states.
Check your understanding
1. A save button says Save but aria-label says Action. What is wrong?
Show worked solution
The accessible name excludes visible text, making speech-input targeting unreliable. Use visible content as the name or an override that includes Save, such as Save draft. Verify the final computed name and keyboard behavior rather than checking attributes alone.
2. What should a failed form submission preserve and communicate?
Show worked solution
Preserve valid entries, identify invalid fields with associated textual guidance, and provide a logical correction path. Choose focus movement for the task and announce summary/status where useful. Retest correction and successful resubmission, not merely the initial red styling.
Key takeaways
- Native elements and stable focus reduce avoidable interaction complexity.
- Accessibility evidence includes dynamic task completion and explicit limits.