Skip to content

linking elements cross boundaries is only possible imperatively and leads to anti-patterns #107

Description

@caridy

Disclosure: This issue is just to brainstorm on the proposed linking mechanism and maybe open other avenues.

A very common use-case is to have custom elements that should be described by sibling elements,

<template>
   <span id="global-one">description for x-foo element</span>
   <fancy-input aria-describedby="global-one">
   </fancy-input>
</template>

Assuming that fancy-input element contains some internal focusable elements, and itself is relying on delegate focus to facilitate the user interaction, e.g.:

<template>
   <span id="global-one">description for x-foo element</span>
   <fancy-input aria-describedby="global-one">
        #shadow-dom (mode=closed, delegateFocus=true)
           <label id="local-one">...</label>
           <input aria-describedby="local-one" />
   </fancy-input>
</template>

It is still impossible to connect those elements from outside (#global-one) and from within (input) without opening some sort of side-channel on fancy-input element. I see two options to achieve this:

  1. expose a method on <fancy-input> custom element that when called with one argument (an element), it sets that element into the internal input's ariaDescribedByElements collection. (with the corresponding guards and deduping).
  2. expose a method on <fancy-input> custom element that when invoked without arguments return a reference to the internal input element.

Either way, it is going to be bad, error prompt or leaky, this is one of those situations when you have to take a poison.

Another option is to observe the aria attributes on the host, walk from the host all the way to the nearest root, and query for the ID from there to try to do some auto-wiring via imperative APIs. This should work fine, but the problem is that since you're in control of the situation as the author of the component, you will have to observe mutations to rewire when needed. This makes the situation very error prompt, while the option 1 and 2 outsource that responsibility to the consumer of the custom element.

Proposal

When discussing this with the team, we were wondering whether or not the delegate focus on the shadow root could be sufficient indication for some sort of compounding mechanism for some of these aria-* attributes that reference IDs. In the example above, you can see that from the consumer perspective (the owner of the template), you can do the regular connecting between the two elements via IDs, they are in the same shadow after all.

But from the component's author perspective, just signaling that the root should receive the focus (via delegateFocus configuration), could be used to build the right tree under the hood that connects the input with both elements (#global-one and #local-one) without the user having to do so manually.

Pros:

  • declarative works again
  • very intuitive (you just don't need to know how the fancy-input works)

Cons:

  • how to opt-out from this behavior? is it possible to opt-out?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions