> For the complete documentation index, see [llms.txt](https://sygnal.gitbook.io/sygnal-webflow-components/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sygnal.gitbook.io/sygnal-webflow-components/cc/sygnal-forms/technical-notes.md).

# Technical Notes

## Technical Challenges

A form input element rendered **inside the code component’s Shadow DOM** will **not** be serialized by the outer HTML `<form>` on submit. Forms only submit light-DOM controls or form-associated custom elements (FACE).&#x20;

Ways to make it work:

### Slot the real input (best, native submit)&#x20;

```html
<!-- In the component's shadow: -->
<slot name="ctrl"></slot>

<!-- In your form (light DOM): -->
<form>
  <code-island> <!-- your component host -->
    <input type="checkbox" slot="ctrl" name="agree" value="1">
  </code-island>
  <button type="submit">Send</button>
</form>
```

The checkbox stays in light DOM → included in submission.

### Mirror to a hidden input (when the component must render its own UI)

```html
<form id="f">
  <code-island id="agree-toggle"></code-island>
  <input type="hidden" name="agree" id="agree-hidden" value="0">
</form>

<script>
// listen for a composed event from the component and mirror the value
document.getElementById('agree-toggle')!.addEventListener('agree-change', (e:any) => {
  document.getElementById('agree-hidden')!.setAttribute('value', e.detail.checked ? '1' : '0');
});
</script>
```

Inside the React component (run in the shadow root), dispatch a composed event on change:

```ts
checkbox.addEventListener('input', () => {
  const host = (checkbox.getRootNode() as ShadowRoot).host as HTMLElement;
  host.dispatchEvent(new CustomEvent('agree-change', { detail:{ checked: checkbox.checked }, bubbles:true, composed:true }));
});
```

### Intercept submit and append programmatically (no hidden input)

```js
const form = document.querySelector('form')!;
form.addEventListener('formdata', (ev) => {
  const host = document.getElementById('agree-toggle')!;
  const checked = (host.shadowRoot!.querySelector('input[type=checkbox]') as HTMLInputElement).checked;
  ev.formData.set('agree', checked ? '1' : '0');
});
```

Pick 1 if you can control markup; 2 if you want native posts with minimal JS; 3 if you already handle submissions via JS.

## Form-Associated Custom Elements (FACE)&#x20;

Form-associated custom elements (FACE) are custom elements that participate in HTML forms like native inputs.

{% hint style="danger" %}
Chromium supported- not just supported in Safari or Firefox.&#x20;
{% endhint %}

Key points

* Opt-in: `static formAssociated = true;` then `this.internals = this.attachInternals()`.
* Association: the element is owned by the nearest `<form>` or by `form="<form id>"` even if placed outside. Access via `this.internals.form`.
* Submission: give the element a `name` attribute and call `this.internals.setFormValue(value)` whenever its value changes; the value is included in form `FormData`.
* Labels: `this.internals.labels` returns associated `<label>`s.
* Validation: use `this.internals.setValidity(...)`, `checkValidity()`, `reportValidity()`, `willValidate`, `validationMessage`.
* Lifecycle hooks: `formAssociated(form)`, `formDisabledCallback(disabled)`, `formResetCallback()`, `formStateRestoreCallback(state,mode)`.

Minimal example (TypeScript)

```ts
class XToggle extends HTMLElement {
  static formAssociated = true;
  private internals: ElementInternals;
  private checked = false;

  constructor() {
    super();
    this.attachShadow({ mode: 'open' }).innerHTML = `
      <button part="btn" type="button" aria-pressed="false">Off</button>
      <style>:host{display:inline-block}</style>`;
    this.internals = this.attachInternals();
  }

  connectedCallback() {
    const btn = this.shadowRoot!.querySelector('button')!;
    btn.addEventListener('click', () => {
      this.checked = !this.checked;
      btn.setAttribute('aria-pressed', String(this.checked));
      // include in form data under this element's name
      this.internals.setFormValue(this.checked ? '1' : '');
      // optional validation example
      const ok = !this.hasAttribute('required') || this.checked;
      this.internals.setValidity(ok ? {} : { valueMissing: true },
                                 ok ? '' : 'Required');
    });
  }

  // Form hooks (optional)
  formResetCallback() {
    this.checked = false;
    this.internals.setFormValue('');
    this.internals.setValidity({});
  }
}
customElements.define('x-toggle', XToggle);
```

Usage (native submit)

```html
<form>
  <x-toggle name="agree" required></x-toggle>
  <button>Submit</button>
</form>
```

Notes

* Must have a `name` to be serialized.
* Works with the `formdata` and normal form submission flows.
* Broad support in Chromium-based browsers and Safari; Firefox does not support FACE as of now.
