Skip to content

Shadow inputs in Form-Associated Custom Elements should be allowed to be hidden from AOM #196

@bennypowers

Description

@bennypowers

In this demo, a form-associated custom element provides a range slider. The FACE' shadow root contains a private input. The developer intends for the shadow input to be transparent to assistive technology, since the FACE exposes it's state to AT via ElementInternals.

We expect AT to report a single interactive control here, but instead find that the Chrome dev tools accessibility panel lists two nested controls. AXE and Lighthouse audits also flag the shadow input as either being unlabeled, or having an illegal aria-hidden attr.

The developer's intent here is to use the native input functionality without reimplementing everything, while making the custom element accessible via ElementInternals. Given this bug, the developer will either have to reimplement the element without using <input> in shadow root (contenteditable, <canvas>, animated SVG, etc), or will have to apply one of several workarounds which are emerging, like moving aria-attributes from light to shadow DOM. All of which seem to run counter to the "intent" of the ElementInternals APIs.

Screenshot from 2023-02-27 09-41-28

Screenshot from 2023-02-27 09-41-21

Please see the reproduction of this bug on my website

Source
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>AOM Repro</title>
  </head>
  <body>
    <header>
      <h1>Shadow Inputs should be allowed to be aria-hidden</h1>
    </header>

    <main>
      <p>In this demo, a form-associated custom element provides a range slider.
        The <abbr title="form-associated custom element">FACE</abbr>' shadow root 
        contains a private input. The private input is meant to be transparent to
        assistive technology, since the FACE exposes it's state to
        <abbr title="assistive technology">AT</abbr> via <code>ElementInternals</code>.</p>
      <p>We expect AT to report a single interactive control here, but instead find that the 
        Chrome dev tools accessibility panel lists two nested controls.</p>

      <form>
        <fieldset>
          <legend>Using <code>aria-hidden="true"</code></legend>
          <label>Range <x-range template-type="aria-hidden"></x-range> </label>
        </fieldset>
        <fieldset>
          <legend>Using <code>role="presentation"</code></legend>
          <label>Range <x-range template-type="presentation"></x-range> </label>
        </fieldset>
      </form>
    </main>

    <template id="aria-hidden">
      <input type="range" aria-hidden="true" min="0" max="100" step="1">
    </template>

    <template id="presentation">
      <input type="range" role="presentation" min="0" max="100" step="1">
    </template>

    <script>
      customElements.define('x-range', class extends HTMLElement {
        static formAssociated = true;

        #internals = this.attachInternals();

        #input;

        constructor() {
          super();
          this.#internals.role = 'slider';
          const type = this.getAttribute('template-type');
          this.attachShadow({ mode: 'open' })
            .append(document.getElementById(type)
              .content.cloneNode(true));
          this.#input = this.shadowRoot.querySelector('input');
          this.#input.addEventListener('change', e => this.#onChange(e))
          if (this.hasAttribute('value')) this.#input.value = this.getAttribute('value');
          this.#update();
        }

        #onChange(event) {
          event.stopPropagation();
          this.#update()
        }

        #update(value = this.#input.value) {
          this.#input.value = value;
          this.#input.step = this.getAttribute('step') ?? '1'
          this.#internals.ariaValueMin = this.getAttribute('min') ?? '0';
          this.#internals.ariaValueMax = this.getAttribute('max') ?? '100';
          this.#internals.ariaValueNow = this.#input.value;
        }
      });
    </script>
  </body>
</html>

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