View on GitHub

Enhance Workshop

This is the Enhance Workshop

Module Index

Module B1: HTML Forms (~30min)

We discussed in Module 3 some of the reasons that we recommend avoiding the Shadow DOM.

Form Behaviors

The following is a partial list of the behaviors to consider when creating a custom element for a form.

User Recommendations

Creating Form Associated Custom Elements with the Shadow DOM and ElementInternals requires properly handling dozens of behaviors that application developers normally get for free with HTML. This is often not apparent from simple examples where only setFormValue() is shown. This specification is a valuable tool for library authors and people building design systems. But partial implementation leaves inaccessible forms. Below is a minimal example of a checkbox with and without the shadowDOM. It takes roughly 80 lines of code to put back most, not all, of the behavior the Shadow DOM breaks. Here is a codepen with this example https://codepen.io/rbethel/pen/zYMeZEd.

<form>
  <label for="light1">light checkbox </label>
  <light-check name="light" input-id="light1"></light-check>
  <label for="shadow1">shadow checkbox</label>
  <shadow-check  name="shadow" id="shadow1"></shadow-check>
  <button type=submit>Submit</button>
  <button type=reset>Reset</button>
</form>

<script>
class LightCheck extends HTMLElement {
  static get observedAttributes() {
    return ['input-id', 'name'];
  }

  constructor() {
    super();
    this.innerHTML = ' <input type="checkbox" /> '
    this.checkbox = this.querySelector('input');
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'input-id') {
      this.checkbox.id = newValue;
    }
    if (name === 'name') {
      this.checkbox.setAttribute('name', newValue)
    }
  }

}

class ShadowCheck extends HTMLElement {
  static formAssociated = true;
  #internals;
  #shadowroot;

  constructor() {
    super();
    this.#shadowroot = this.attachShadow({ mode: 'open' });
    this.#internals = this.attachInternals();
    this.#internals.setFormValue('false');

    this.#internals.role = "checkbox"
    this.#internals.ariaChecked = "false"

    this.#shadowroot.innerHTML = ' <input type="checkbox" /> '
    this.checkbox = this.#shadowroot.querySelector('input');
    this.onChange = this.onChange.bind(this);

    this.handleImplicitSubmit = this.handleImplicitSubmit.bind(this);
    this.checkbox.addEventListener('keypress', this.handleImplicitSubmit)
    this.toggle = this.toggle.bind(this);

    this.checkbox.addEventListener('change', this.onChange);
    document.querySelector('label[for=' + this.id + ']').addEventListener('click', this.toggle)
    this.#internals.setValidity({
      customError: true
      }, 'You must check this box', this.checkbox);

  }

  formResetCallback(){
    this.checkbox.checked = false
    this.#internals.setFormValue('');
    this.#internals.setValidity({
      customError: true
      }, 'You must check this box', this.checkbox);
  }

  onChange() {
    if (this.checkbox.checked) {
      this.#internals.setFormValue(this.checkbox.value || 'on');
    } else {
      this.#internals.setFormValue('');
    }


    if (this.checkbox.checked) {
      this.#internals.ariaChecked = "true"
    } else {
      this.#internals.ariaChecked = "false"
    }

    if (this.checkbox.checked) {
      this.#internals.setValidity({});
    } else {
      this.#internals.setValidity({
        customError: true
        }, 'You must check this box', this.checkbox);
    }
  }

  handleImplicitSubmit(event) {
    if (event.key === 'Enter') {
      event.preventDefault();
      this.#internals.form.requestSubmit();
    }
  }

  toggle() {
    this.checkbox.checked = !this.checkbox.checked
    this.onChange()
  }


}

  customElements.define('shadow-check', ShadowCheck);
  customElements.define('light-check', LightCheck);

  document.querySelector('form').addEventListener('submit', event => {
    event.preventDefault();
    const formData = new FormData(event.target);

    console.log(Array.from(formData.entries()));
  });
</script>

Problems/Concerns

References/Resources