Let me be perfectly clear, this script was generated by ChatGPT. It came to be from a problem I encountered when QAing a WCAG 2.2 assessment. I knew that the advice provided was not right, but didn’t know what was going on. I suspected it was something to do with aria-activedescendant
implementation (in a custom menu in this case). But could not work out what, so I asked ChatGPT to help create a script to help debug aria-activedescendant
.
I am making it available for others who like me suffer from I’m shit at it (JavaScript) syndrome who may find it useful, and just maybe someone who has the JavaScript chops to improve it.
The Gist
Read me
aria-activedescendant Console Inspector – Documentation
What it is
A zero-install snippet you paste into your browser’s DevTools Console to verify aria-activedescendant
wiring on any page. It watches focus and relevant DOM changes, then prints a single-line status showing
whether the focused element’s aria-activedescendant
resolves to a valid target and how that target is related.
Quick start
- Open the target page
- Open DevTools → Console
- Paste the script and press Enter
- Move focus with the keyboard (e.g., arrow keys in a
combobox/listbox)
You’ll see log lines like:
[aria-activedescendant] ✅ owner=input#search(role=combobox) activedescendant="opt-3" target=div#opt-3(role=option) rel=owned/controlled role(owner/target)=combobox/option :: ✅ OK
What it reports
Each log line contains:
- owner — The currently focused element, formatted as
tag#id (role=…)
. - activedescendant — The ID value from
aria-activedescendant
on the owner (or—
if absent). - target — The element that ID resolves to, similarly formatted
(or—
if missing). - rel — Relationship between owner and target:
descendant
→ target is inside the ownerowned/controlled
→ target is inside any element referenced by
owner’saria-owns
oraria-controls
unrelated
→ none of the above
- role(owner/target) — The roles found on both elements.
- status — Either
✅ OK
or⚠️ …
with reasons joined by;
(e.g.,not descendant/owned
,target hidden
,owner hidden
,
no target
,missing aria-activedescendant
).
How it works (high level)
- Focus & keyboard listeners trigger a check after each event.
- A MutationObserver monitors attribute/DOM changes on the
document (includingaria-activedescendant
,aria-owns
,
aria-controls
,role
,id
,style
,class
) and re-evaluates
when any change occurs. - The evaluation:
- Reads the focused element (“owner”) and its
aria-activedescendant
value - Looks up the target element by ID
- Determines relation: descendant vs inside any
aria-owns
/aria-controls
container vs unrelated - Checks visibility of both owner and target
- Prints a one-line summary with ✅/⚠️
- Reads the focused element (“owner”) and its
Console API
- Stop the inspector:
window.__stopAadInspector()
- Toggle verbose mode (prints the concise line plus a full info
object):window.__aadVerbose(true) // on window.__aadVerbose(false) // off
Typical use cases
- Validate combobox/listbox, grid, tree, menu, tablist patterns that
drive focus viaaria-activedescendant
. - Confirm that the active option (or row/treeitem/tab) exists, is
visible, and is correctly related to its owner. - Catch common mistakes:
- Owner has no
aria-activedescendant
- Target ID doesn’t exist
- Target lives outside expected containers
- Owner/target hidden via CSS
- Owner has no
Tips & caveats
- This tool checks DOM wiring and visibility heuristics. It does
not verify assistive technology announcements or user agent
mapping. - If the page is extremely dynamic, the MutationObserver’s broad scope
(includingstyle
andclass
) may be chatty. If needed, you can:- Switch off verbose logs:
__aadVerbose(false)
- Stop the current run and re-paste a trimmed variant with a
reducedattributeFilter
(e.g., onlyaria-activedescendant
androle
).
- Switch off verbose logs:
- “Owned/controlled” includes elements referenced by either
aria-owns
oraria-controls
. That’s helpful in practice, though
note thataria-controls
is association, not parent/child
semantics.
Troubleshooting
- No output? Ensure DevTools Console is open and you pressed Enter after pasting.
- Too many logs / sluggish page?
- Run
__aadVerbose(false)
(default is already concise). - Run
__stopAadInspector()
to stop, then re-run a trimmed script
with fewer observed attributes.
- Run
- Target reads as
—
: Check that the owner’s
aria-activedescendant
value exactly matches an element ID on
the page.
Removal
At any time:
__stopAadInspector()
Further reading
Jerkin Back and Forth
Lyrics
I know I let you tell me what to do You were confident you knew best Now things aren't working like you want them to Your confidence is what I detest You got me looking up high You got me searching down low You got me, I know you know You got me jerkin' back 'n' forth You told me, people like to suffer You told me that's the way it is You said that things were getting better You said I should accept all this You think it's funny But what I say is true The reason that I live like this Is all because of you You got me looking up high You got me searching down low You got me, I know you know You got me jerkin' back 'n' forth There is a thought that keeps me thinking Like a stone inside my shoe It is a vision reoccurring A dirty window, I can see you through You think it's funny But what I say is true The reason that I live like this Is all because of you You got me looking up high You got me searching down low You got me, I know you know You got me jerkin' back 'n' forth You got me looking up high You got me searching down low You got me, I know you know You got me jerkin' back 'n' forth
One reply on “active-descendant console debugger”
[…] active-descendant console debugger Steve Faulkner: I asked ChatGPT to help create a script to help debug aria-activedescendant. […]