My understanding used to be that a list item <li> tag had to be the direct child of a <ul>, <ol> or <menu> tag.
But I've been using web components and autonomous custom elements recently. And I've read that for autonomous custom elements, the content model is transparent. See https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-core-concepts
I want to have an autonomous custom element <my-list> which has a <ul> in its shadowDOM (or one of the other valid parents, such as ol and menu), and a second autonomous custom element <my-list-item> with an <li> in its shadowDOM. (See "the example" below.)
Why? A few reasons:
- css styling scoped to the shadowDOM
- additional event handlers
- some use cases might have more going on, like each
<li>containing shadowDOM children besides just the<slot> - I'm using Lit 3, so its natural to write a web component
Is something like this example below valid or invalid, according to the HTML 5 spec, and according to accessibility guidelines?
Note: I'm using the <template shadowrootmode="open"> (see here https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template#shadowrootmode ) to make my example more self contained. In my real code, I'm using the Lit framework, which creates the shadowDOM programmatically.
The example:
<my-list role="presentation">
<template shadowrootmode="open">
<ul>
<slot />
</ul>
</template>
<my-list-item role="presentation">
<template shadowrootmode="open">
<li>
<slot />
</li>
</template>
My text here
</my-list-item>
</my-list>
Another reasons I think this might be valid is that I see that axe-core has a unit test that looks similar:
https://github.com/dequelabs/axe-core/blob/develop/test/checks/lists/only-listitems.js#L224-L232
it('should return false in a shadow DOM pass', () => {
const node = document.createElement('div');
node.innerHTML = '<li>My list item </li>';
const shadow = node.attachShadow({ mode: 'open' });
shadow.innerHTML = '<ul><slot></slot></ul>';
const checkArgs = checkSetup(node, 'ul');
assert.isFalse(checkEvaluate.apply(checkContext, checkArgs));
});
But it looks like that is a specific mode / option in axe-core and not the default behavior?
In theory, whatever you have said makes sense. It means following things are true:
So, it means that you should be allowed to create completely custom lists. So, this should work:
In this case, the
<ul>lives a different shadow root than those of<li>elements. For this above definition, the browser ends up generating the following accessibility tree (from the Chrome browser):So, as per specs, the browser is properly generating the accessibility tree which is was most of the tools should see. My best guess is that
axe-coreis relying on the same specs for this given test. But there in practice, things are different.<ul>and<ol>still explicitly restrict them to have only<li>,<script>and<template>. (This should be addressed in future).So, as per spec, you are good to have custom list elements within your custom list in a completely different Shadow DOM, however in practice, the accessibility will still take a hit. If possible, better to rely on classical
ul > liorol > lihierarchy.