Web components have been able to fully participate in native HTML forms since 2023 — submit their value with FormData, trigger :invalid, reset alongside the <form>, the works. The API that makes it possible, ElementInternals, is now supported in Chromium, Firefox, and Safari 16+.

The catch: nobody tells you it's about 40 lines of repetitive plumbing per component. This post walks through what that plumbing looks like, then shows a small reusable mixin (form-control-mixin) that gets rid of it.

What "form-associated" actually buys you

A form-associated custom element can:

submit a value as part of the form's FormData, under its name attribute