{"id":1706,"date":"2025-09-03T11:24:19","date_gmt":"2025-09-03T11:24:19","guid":{"rendered":"https:\/\/html5accessibility.com\/stuff\/?p=1706"},"modified":"2025-09-03T13:28:38","modified_gmt":"2025-09-03T13:28:38","slug":"active-descendant-console-debugger","status":"publish","type":"post","link":"https:\/\/html5accessibility.com\/stuff\/2025\/09\/03\/active-descendant-console-debugger\/","title":{"rendered":"active-descendant console debugger"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-1708 size-full\" src=\"https:\/\/html5accessibility.com\/stuff\/wp-content\/uploads\/2025\/09\/Screenshot-2025-09-03-113133.jpg\" alt=\"Not Knowing has detrimental effects Then: pic of me, young full of life and curly long brown hair. Now: pic of me much older, short greyish hair weraing a plastic false set of lips, that skewed my mouth into a form of existential scream.\" width=\"563\" height=\"417\" srcset=\"https:\/\/html5accessibility.com\/stuff\/wp-content\/uploads\/2025\/09\/Screenshot-2025-09-03-113133.jpg 563w, https:\/\/html5accessibility.com\/stuff\/wp-content\/uploads\/2025\/09\/Screenshot-2025-09-03-113133-300x222.jpg 300w\" sizes=\"auto, (max-width: 563px) 100vw, 563px\" \/><\/p>\n<p class=\"note\"><strong>Let me be perfectly clear<\/strong>, 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\u00a0 was not right, but didn&#8217;t know what was going on. I suspected it was something to do with <code>aria-activedescendant<\/code> implementation (in a custom <em>menu<\/em> in this case). But could not work out what, so I asked ChatGPT to help create a script to help debug <code>aria-activedescendant<\/code>.<\/p>\n<p>I am making it available for others who like me suffer from <em>I&#8217;m shit at it (JavaScript) syndrome<\/em> who may find it useful, and <em>just maybe<\/em> someone who has the JavaScript chops to improve it.<\/p>\n<h2>The Gist<\/h2>\n<p><script src=\"https:\/\/gist.github.com\/stevefaulkner\/a0f1f13c620a83e9584a4caae3c9808d.js\"><\/script><\/p>\n<h2>Read me<\/h2>\n<h3>aria-activedescendant Console Inspector &#8211; Documentation<\/h3>\n<h4>What it is<\/h4>\n<p>A zero-install snippet you paste into your browser&#8217;s DevTools Console to <strong>verify\u00a0<code class=\"notranslate\">aria-activedescendant<\/code>\u00a0wiring<\/strong>\u00a0on any page. It watches focus and relevant DOM changes, then prints a\u00a0<strong>single-line status<\/strong>\u00a0showing<br \/>\nwhether the focused element&#8217;s\u00a0<code class=\"notranslate\">aria-activedescendant<\/code>\u00a0resolves to a valid target and how that target is related.<\/p>\n<h4>Quick start<\/h4>\n<ol>\n<li>Open the target page<\/li>\n<li>Open\u00a0<strong>DevTools \u2192 Console<\/strong><\/li>\n<li>Paste the script and press\u00a0<strong>Enter<\/strong><\/li>\n<li>Move focus with the keyboard (e.g., arrow keys in a<br \/>\ncombobox\/listbox)<\/li>\n<\/ol>\n<p>You&#8217;ll see log lines like:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate\">[aria-activedescendant] \u2705 owner=input#search(role=combobox) activedescendant=\"opt-3\" target=div#opt-3(role=option) rel=owned\/controlled role(owner\/target)=combobox\/option :: \u2705 OK\r\n<\/code><\/pre>\n<h4>What it reports<\/h4>\n<p>Each log line contains:<\/p>\n<ul>\n<li><strong>owner<\/strong>\u00a0&#8212; The currently focused element, formatted as<br \/>\n<code class=\"notranslate\">tag#id (role=\u2026)<\/code>.<\/li>\n<li><strong>activedescendant<\/strong>\u00a0&#8212; The ID value from\u00a0<code class=\"notranslate\">aria-activedescendant<\/code><br \/>\non the owner (or\u00a0<code class=\"notranslate\">\u2014<\/code>\u00a0if absent).<\/li>\n<li><strong>target<\/strong>\u00a0&#8212; The element that ID resolves to, similarly formatted<br \/>\n(or\u00a0<code class=\"notranslate\">\u2014<\/code>\u00a0if missing).<\/li>\n<li><strong>rel<\/strong>\u00a0&#8212; Relationship between owner and target:\n<ul dir=\"auto\">\n<li><code class=\"notranslate\">descendant<\/code> \u2192 target is inside the owner<\/li>\n<li><code class=\"notranslate\">owned\/controlled<\/code>\u00a0\u2192 target is inside any element referenced by<br \/>\nowner&#8217;s\u00a0<code class=\"notranslate\">aria-owns<\/code>\u00a0or\u00a0<code class=\"notranslate\">aria-controls<\/code><\/li>\n<li><code class=\"notranslate\">unrelated<\/code>\u00a0\u2192 none of the above<\/li>\n<\/ul>\n<\/li>\n<li><strong>role(owner\/target)<\/strong>\u00a0&#8212; The roles found on both elements.<\/li>\n<li><strong>status<\/strong>\u00a0&#8212; Either\u00a0<code class=\"notranslate\">\u2705 OK<\/code>\u00a0or\u00a0<code class=\"notranslate\">\u26a0\ufe0f \u2026<\/code>\u00a0with reasons joined by\u00a0<code class=\"notranslate\">;<\/code><br \/>\n(e.g.,\u00a0<code class=\"notranslate\">not descendant\/owned<\/code>,\u00a0<code class=\"notranslate\">target hidden<\/code>,\u00a0<code class=\"notranslate\">owner hidden<\/code>,<br \/>\n<code class=\"notranslate\">no target<\/code>,\u00a0<code class=\"notranslate\">missing aria-activedescendant<\/code>).<\/li>\n<\/ul>\n<h4>How it works (high level)<\/h4>\n<ul>\n<li><strong>Focus &amp; keyboard listeners<\/strong> trigger a check after each event.<\/li>\n<li>A\u00a0<strong>MutationObserver<\/strong>\u00a0monitors attribute\/DOM changes on the<br \/>\ndocument (including\u00a0<code class=\"notranslate\">aria-activedescendant<\/code>,\u00a0<code class=\"notranslate\">aria-owns<\/code>,<br \/>\n<code class=\"notranslate\">aria-controls<\/code>,\u00a0<code class=\"notranslate\">role<\/code>,\u00a0<code class=\"notranslate\">id<\/code>,\u00a0<code class=\"notranslate\">style<\/code>,\u00a0<code class=\"notranslate\">class<\/code>) and re-evaluates<br \/>\nwhen any change occurs.<\/li>\n<li>The evaluation:\n<ol>\n<li>Reads the focused element (&#8220;owner&#8221;) and its<br \/>\n<code class=\"notranslate\">aria-activedescendant<\/code> value<\/li>\n<li>Looks up the\u00a0<strong>target<\/strong> element by ID<\/li>\n<li>Determines relation: descendant vs inside any<br \/>\n<code class=\"notranslate\">aria-owns<\/code>\/<code class=\"notranslate\">aria-controls<\/code> container vs unrelated<\/li>\n<li>Checks visibility of\u00a0<strong>both<\/strong> owner and target<\/li>\n<li>Prints a one-line summary with \u2705\/\u26a0\ufe0f<\/li>\n<\/ol>\n<\/li>\n<\/ul>\n<h4>Console API<\/h4>\n<ul>\n<li><strong>Stop<\/strong>\u00a0the inspector:\n<div>\n<pre class=\"notranslate\"><span class=\"pl-smi\">window<\/span><span class=\"pl-kos\">.<\/span><span class=\"pl-en\">__stopAadInspector<\/span><span class=\"pl-kos\">(<\/span><span class=\"pl-kos\">)<\/span><\/pre>\n<\/div>\n<\/li>\n<li><strong>Toggle verbose mode<\/strong>\u00a0(prints the concise line plus a full info<br \/>\nobject):<\/p>\n<div>\n<pre class=\"notranslate\"><span class=\"pl-smi\">window<\/span><span class=\"pl-kos\">.<\/span><span class=\"pl-en\">__aadVerbose<\/span><span class=\"pl-kos\">(<\/span><span class=\"pl-c1\">true<\/span><span class=\"pl-kos\">)<\/span>   <span class=\"pl-c\">\/\/ on<\/span>\r\n<span class=\"pl-smi\">window<\/span><span class=\"pl-kos\">.<\/span><span class=\"pl-en\">__aadVerbose<\/span><span class=\"pl-kos\">(<\/span><span class=\"pl-c1\">false<\/span><span class=\"pl-kos\">)<\/span>  <span class=\"pl-c\">\/\/ off<\/span><\/pre>\n<\/div>\n<\/li>\n<\/ul>\n<h4 dir=\"auto\">Typical use cases<\/h4>\n<ul dir=\"auto\">\n<li>Validate combobox\/listbox, grid, tree, menu, tablist patterns that<br \/>\ndrive focus via\u00a0<code class=\"notranslate\">aria-activedescendant<\/code>.<\/li>\n<li>Confirm that the active option (or row\/treeitem\/tab)\u00a0<strong>exists<\/strong>, is<br \/>\n<strong>visible<\/strong>, and is\u00a0<strong>correctly related<\/strong>\u00a0to its owner.<\/li>\n<li>Catch common mistakes:\n<ul dir=\"auto\">\n<li>Owner has no\u00a0<code class=\"notranslate\">aria-activedescendant<\/code><\/li>\n<li>Target ID doesn&#8217;t exist<\/li>\n<li>Target lives outside expected containers<\/li>\n<li>Owner\/target hidden via CSS<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h4 dir=\"auto\">Tips &amp; caveats<\/h4>\n<ul dir=\"auto\">\n<li>This tool checks\u00a0<strong>DOM wiring<\/strong>\u00a0and visibility heuristics. It does<br \/>\n<strong>not<\/strong>\u00a0verify assistive technology announcements or user agent<br \/>\nmapping.<\/li>\n<li>If the page is extremely dynamic, the MutationObserver&#8217;s broad scope<br \/>\n(including\u00a0<code class=\"notranslate\">style<\/code>\u00a0and\u00a0<code class=\"notranslate\">class<\/code>) may be chatty. If needed, you can:<\/p>\n<ul dir=\"auto\">\n<li>Switch off verbose logs:\u00a0<code class=\"notranslate\">__aadVerbose(false)<\/code><\/li>\n<li>Stop the current run and re-paste a trimmed variant with a<br \/>\nreduced\u00a0<code class=\"notranslate\">attributeFilter<\/code>\u00a0(e.g., only\u00a0<code class=\"notranslate\">aria-activedescendant<\/code><br \/>\nand\u00a0<code class=\"notranslate\">role<\/code>).<\/li>\n<\/ul>\n<\/li>\n<li>&#8220;Owned\/controlled&#8221; includes elements referenced by\u00a0<strong>either<\/strong><br \/>\n<code class=\"notranslate\">aria-owns<\/code>\u00a0or\u00a0<code class=\"notranslate\">aria-controls<\/code>. That&#8217;s helpful in practice, though<br \/>\nnote that\u00a0<code class=\"notranslate\">aria-controls<\/code>\u00a0is\u00a0<strong>association<\/strong>, not parent\/child<br \/>\nsemantics.<\/li>\n<\/ul>\n<h2 dir=\"auto\">Troubleshooting<\/h2>\n<ul dir=\"auto\">\n<li><strong>No output?<\/strong> Ensure DevTools Console is open and you pressed Enter after pasting.<\/li>\n<li><strong>Too many logs \/ sluggish page?<\/strong>\n<ul dir=\"auto\">\n<li>Run\u00a0<code class=\"notranslate\">__aadVerbose(false)<\/code> (default is already concise).<\/li>\n<li>Run\u00a0<code class=\"notranslate\">__stopAadInspector()<\/code>\u00a0to stop, then re-run a trimmed script<br \/>\nwith fewer observed attributes.<\/li>\n<\/ul>\n<\/li>\n<li><strong>Target reads as\u00a0<code class=\"notranslate\">\u2014<\/code><\/strong>: Check that the owner&#8217;s<br \/>\n<code class=\"notranslate\">aria-activedescendant<\/code>\u00a0value exactly matches an element\u00a0<strong>ID<\/strong>\u00a0on<br \/>\nthe page.<\/li>\n<\/ul>\n<h2 dir=\"auto\">Removal<\/h2>\n<p dir=\"auto\">At any time:<\/p>\n<pre class=\"notranslate\"><span class=\"pl-en\">__stopAadInspector<\/span><span class=\"pl-kos\">(<\/span><span class=\"pl-kos\">)<\/span><\/pre>\n<h2>Further reading<\/h2>\n<ul>\n<li><a href=\"https:\/\/www.w3.org\/WAI\/ARIA\/apg\/practices\/keyboard-interface\/#kbd_focus_activedescendant\">Managing Focus in Composites Using aria-activedescendant<\/a><\/li>\n<li><a href=\"https:\/\/w3c.github.io\/aria\/#aria-activedescendant\">aria-activedescendant property<\/a><\/li>\n<\/ul>\n<h2>Jerkin Back and Forth<\/h2>\n<p><iframe loading=\"lazy\" title=\"YouTube video player\" src=\"https:\/\/www.youtube.com\/embed\/xFvAmY3kkO0?si=DEreSwkQEm2Mro36&amp;start=126\" width=\"560\" height=\"315\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<details>\n<summary>Lyrics<\/summary>\n<pre>I know I let you tell me what to do\r\nYou were confident you knew best\r\nNow things aren't working like you want them to\r\nYour confidence is what I detest\r\nYou got me looking up high\r\nYou got me searching down low\r\nYou got me, I know you know\r\nYou got me jerkin' back 'n' forth\r\nYou told me, people like to suffer\r\nYou told me that's the way it is\r\nYou said that things were getting better\r\nYou said I should accept all this\r\nYou think it's funny\r\nBut what I say is true\r\nThe reason that I live like this\r\nIs all because of you\r\nYou got me looking up high\r\nYou got me searching down low\r\nYou got me, I know you know\r\nYou got me jerkin' back 'n' forth\r\nThere is a thought that keeps me thinking\r\nLike a stone inside my shoe\r\nIt is a vision reoccurring\r\nA dirty window, I can see you through\r\nYou think it's funny\r\nBut what I say is true\r\nThe reason that I live like this\r\nIs all because of you\r\nYou got me looking up high\r\nYou got me searching down low\r\nYou got me, I know you know\r\nYou got me jerkin' back 'n' forth\r\nYou got me looking up high\r\nYou got me searching down low\r\nYou got me, I know you know\r\nYou got me jerkin' back 'n' forth\r\n<\/pre>\n<\/details>\n","protected":false},"excerpt":{"rendered":"<p>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\u00a0 was not right, but didn&#8217;t know what was going on. I suspected it was something to do with aria-activedescendant implementation (in a custom [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1706","post","type-post","status-publish","format-standard","hentry","category-htmlaccessibility"],"_links":{"self":[{"href":"https:\/\/html5accessibility.com\/stuff\/wp-json\/wp\/v2\/posts\/1706","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/html5accessibility.com\/stuff\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/html5accessibility.com\/stuff\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/html5accessibility.com\/stuff\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/html5accessibility.com\/stuff\/wp-json\/wp\/v2\/comments?post=1706"}],"version-history":[{"count":14,"href":"https:\/\/html5accessibility.com\/stuff\/wp-json\/wp\/v2\/posts\/1706\/revisions"}],"predecessor-version":[{"id":1721,"href":"https:\/\/html5accessibility.com\/stuff\/wp-json\/wp\/v2\/posts\/1706\/revisions\/1721"}],"wp:attachment":[{"href":"https:\/\/html5accessibility.com\/stuff\/wp-json\/wp\/v2\/media?parent=1706"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/html5accessibility.com\/stuff\/wp-json\/wp\/v2\/categories?post=1706"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/html5accessibility.com\/stuff\/wp-json\/wp\/v2\/tags?post=1706"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}