Thanks to all for their responses. The fact that I misread a bunch of authoring 
requirements as UA requirements made things a lot more complicated than they 
are in reality.

I updated my ARIA summary [1] and illustrative scenarios [2] to reflect the 
actual spec/browser behavior. And, given the much-easier requirements, I was 
able to draft up a pretty simple solution:

https://gist.github.com/domenic/8ae33f320b856a9aef43

I still need to investigate whether this kind of solution is feasible from an 
implementation perspective, but from an author perspective it seems natural and 
easy to use.

Would love to hear what others think.

[1]: https://gist.github.com/domenic/ae2331ee72b3847ce7f5
[2]: https://gist.github.com/domenic/bc8a36d9608d65bd7fa9


-----Original Message-----
From: Domenic Denicola [mailto:[email protected]] 
Sent: Wednesday, August 27, 2014 19:43
To: public-webapps
Subject: [Custom] Custom elements and ARIA

TL;DR: we (Google) are trying to explain the platform with custom elements [1], 
and noticed they can't do ARIA as well as native elements. We would like to 
prototype a solution, ideally as a standardized API that we can let authors use 
too. (If that doesn't work, then we can instead add a non-web-exposed API that 
we can use inside Chrome, but that would be a shame.) A succinct statement of 
the problem is that we need some way to explain [3]. The rest of this mail 
explains the problem in great depth in the hopes other people are interested in 
designing a solution with me.

Also, in the course of figuring all this out, I put together an intro to the 
relevant aspects of ARIA, which you might find useful, at [2].

## The problem

Right now, custom elements can manually add ARIA roles and stoperties (= states 
or properties) to themselves, by setting attributes on themselves. In practice, 
this kind of allows them to have default ARIA roles and stoperties, but they 
are fragile and exposed in a way that is incongruous with the capabilities of 
native elements in this regard.

For example, if we were to implement `<hr>` as a custom element, we would 
attempt to give it the separator role by doing `this.setAttribute("role", 
"separator")` in the `createdCallback`. However, if the author then did 
`document.querySelector("custom-hr").setAttribute("role", "menuitem")`, 
assistive technology would reflect our `<custom-hr>` as a menu item, and not as 
a separator. So **unlike native elements, custom elements cannot have 
non-overridable semantics**.

Furthermore, even if the author wasn't overriding the role attribute, there 
would still be a difference between `<hr>` and `<custom-hr>`. Namely, 
`document.querySelector("hr").getAttribute("role") === null`, whereas 
`document.querySelector("custom-hr").getAttribute("role") === "separator"`. So 
**unlike native elements, custom elements cannot have default ARIA roles or 
stoperties without them being reflected in the DOM**.

As another example, imagine trying to implement `<button>` as a custom element. 
To enforce the restriction to a role of either `button` or `menuitem`, the 
custom element implementation would need to use its `attributeChangedCallback` 
to revert changes that go outside those possibilities. And that of course only 
occurs at the end of the microtask, so in the meantime screen-readers are 
giving their users bad information. And even then, the experience of the 
attribute value being reverted for the custom element is not the same as that 
for a native element, where the attribute value stays the same but the true 
ARIA role as reflected to screenreaders remains `button`. So: **unlike native 
elements, custom elements cannot robustly restrict their ARIA roles to certain 
values**.

Finally, consider how `<details>` synchronizes `aria-expanded` with the `open` 
attribute. To implement this with custom elements, you would use the 
`attributeChangedCallback` to set an `aria-expanded` attribute. But again, the 
author could change it via `setAttribute`, causing it to be out-of-sync. The 
takeaway here is that **unlike native elements, custom elements cannot reserve 
the management of certain stoperties for themselves**.

In the end, trying to manage one's ARIA state HTML attributes is fragile, and 
lacks the conceptual stratification and the resultant power of the  internal 
state/mutable HTML attribute approach used by native elements. [3] illustrates 
more drastically the differences between the internal state and the HTML 
elements in many representative cases.

## Discussion

In my own musings, I came up with a few solutions [4], but they are pretty icky 
to program against. There are a few tough issues that emerge, e.g.

- Do we allow programmatic changing of *all* elements roles/stoperties, e.g. 
via a new API `element.aria.role = "whatever"`, or do we want some kind of 
model that reserves that power for the author of the element code? The former 
allows bad things like a <hr> with role menuitem, but the latter makes for 
tricky programming.

- If an element wants to manage some aspect of its ARIA state itself, e.g. 
restrict its role, how does that impact the rest of its ARIA stuff? E.g., how 
can I signal that I want to manage aria-expanded, ignoring any 
attribute-setting that goes on, while at the same time allowing the author to 
mess with aria-hidden at will? Or even more complex, how can I signal that I 
want to have a default way of calculating aria-level, but if the author sets 
it, their value overrides it (until they removeAttribute)?

- Trying for a declarative solution, e.g. `"aria-expanded": function () { 
return this.hasAttribute("open"); }` gets you 90% of the way there, but you 
need some way of triggering an "ARIA recalc." E.g. <details> would trigger an 
ARIA recalc when its open="" attribute changes, and h1 would trigger an ARIA 
recalc whenever its position within the document changes (because that 
potentially affects outline level), and input would trigger an ARIA recalc 
under any situation where a <datalist> could become associated with it. If you 
accept this premise, then who has the power to trigger an ARIA recalc? Who has 
the responsibility?

I would welcome anyone else's ideas on solutions. The cases that need to be 
addressed are summed up, somewhat representatively, in [3]. If nobody is 
interested I'll probably just evolve my existing kinda-icky solutions, and code 
one of them into Chrome as a non-web-exposed API (or maybe expose it behind a 
flag).

[1]: https://github.com/dglazkov/html-as-custom-elements
[2]: https://gist.github.com/domenic/ae2331ee72b3847ce7f5
[3]: https://gist.github.com/domenic/bc8a36d9608d65bd7fa9
[4]: https://gist.github.com/domenic/4ddde9bdcb673c48f237

Reply via email to