accname icon indicating copy to clipboard operation
accname copied to clipboard

How is the accessible name determined when an element contains a slot?

Open geoffrich opened this issue 5 years ago • 7 comments

How is the accessible name determined for elements that contain slots? For example, look at the following HTML which defines a custom button element containing a slot.

<template id="button-template">
    <button><slot></slot></button>
</template>
<custom-button>Submit</custom-button>
<script>
    customElements.define(
    'custom-button',
    class extends HTMLElement {
        constructor() {
        super();
        const template = document.getElementById('button-template')
            .content;
        const shadowRoot = this.attachShadow({ mode: 'open' }).appendChild(
            template.cloneNode(true)
        );
        }
    }
    );
</script>

When inspecting the <button> element in the shadow root, both Chrome and Firefox say the accessible name is "Submit". However, I can't find anything in the accessible name mapping spec mentioning slots. Per step F.iii, each child node of the button would be recursively examined to determine the name. So, the text alternative for the slot would need to be determined. The slot does not have any childNodes, so the process stops there. It seems like assignedNodes would also need to be examined to compute the accessible name.

const slot = document.querySelector('custom-button').shadowRoot.querySelector('slot')
console.log(slot.childNodes)        // NodeList []
console.log(slot.assignedNodes())   // Array [ #text "Custom name" ]
  • When the spec says "For each child node of the current node," does it include assigned nodes when slots are being examined?
  • Should it be updated to mention assigned nodes or am I misunderstanding the spec?

The browser implementations seem to be looking at assigned nodes as well as child nodes, so I'm wondering why the spec doesn't mention them.

geoffrich avatar Sep 04 '20 23:09 geoffrich

This doesn't sound like something accname should be defining. This should be defined in HTML-AAM IMO

jnurthen avatar Sep 04 '20 23:09 jnurthen

Accessible name for a button is defined at https://www.w3.org/TR/html-aam-1.0/#button-element-accessible-name-computation As there is no aria-label or aria-labelledby on the button Accname is not involved. Hence closing this. If html-aam does not correctly answer the question then I encourage you to open an issue there.

jnurthen avatar Sep 05 '20 00:09 jnurthen

@jnurthen

As there is no aria-label or aria-labelledby on the button Accname is not involved

Unfortunately I do not understand the argument. Accname is the general specification for determining names, not only in relation to aria-label and aria-labelledby. If custom elements (general, not only as buttons) are a special case concerning the determination of names, this should be considered in Accname.

I am not an expert for custom elements and so I cannot say if Accname currently describes the determination of names (and description) of custom elements correctly and completely. If this is not the case, please reopen the issue

JAWS-test avatar Sep 05 '20 04:09 JAWS-test

Yeah, to me this seems to be a special case not covered by the spec. I can also easily contrive an example using aria-labelledby that has the same issue.

<template id="button-template">
    <div id="label">
        <slot></slot>
    </div>
    <button aria-labelledby="label">Name 1</button>
</template>
<custom-button>Submit</custom-button>
<script>
    customElements.define(
        'custom-button',
        class extends HTMLElement {
            constructor() {
                super();
                const template = document.getElementById('button-template')
                    .content;
                const shadowRoot = this.attachShadow({ mode: 'open' }).appendChild(
                    template.cloneNode(true)
                );
            }
        }
    );
</script>

The button within the custom element is labeled by another element whose content is a slot. However, the text node "Submit" is not a child node of the element labeling the button -- it is an assigned node, a concept (I believe) unique to slots. It seems like a strict interpretation of the spec would ignore assigned nodes and thus be unable to determine the accessible name. Despite that, Chrome and Firefox both identify the name of the button to be Submit (which is the desired behavior).

If you pop open my example in a browser and open dev tools you can see the difference. The "Submit" text node is outside the slot and is not a child node. image

Should "child nodes" be understood to include assigned nodes as well, and if so, should this be clarified in the spec?

geoffrich avatar Sep 07 '20 03:09 geoffrich

If you would prefer another example, let me know. I should be able to demonstrate this issue with any element that needs to determine an accessible name -- simply place a slot where the name should go.

geoffrich avatar Sep 07 '20 03:09 geoffrich

Happy to reopen now there is ARIA involved and the spec is invoked!

jnurthen avatar Sep 07 '20 20:09 jnurthen

Also note the edge case where a slot has default content. In this case, the accessible name should be based on the slot's child nodes OR the slot's assigned nodes, depending on if content is passed.

<template id="button-template">
  <div id="label">
    <slot>Default name</slot>
  </div>
  <button aria-labelledby="label">Name 1</button>
</template>
<custom-button></custom-button>
<custom-button>Custom name</custom-button>

When nothing is passed to the slot, the accessible name should be based on the default content (which is a child node)

const slot1 = document
  .querySelectorAll('custom-button')[0]
  .shadowRoot.querySelector('slot');

console.log(slot1.childNodes); // NodeList [ #text "Default name"]
console.log(slot1.assignedNodes()); // Array [  ]

When text is passed to the slot, the accessible name calculation should ignore child nodes (which still contains the default content) and use assigned nodes instead.

const slot2 = document
  .querySelectorAll('custom-button')[1]
  .shadowRoot.querySelector('slot');
console.log(slot2.childNodes); // NodeList [#text "Default name"]
console.log(slot2.assignedNodes()); // Array [ #text "Custom name" ]

As in the previous examples, browsers (at least Chrome and Firefox, not sure about Safari) are implementing this as desired. These cases simply don't seem to be considered in the spec.

geoffrich avatar Sep 09 '20 23:09 geoffrich