diff --git a/rfcs/NNN-form-associated-mixin.md b/rfcs/NNN-form-associated-mixin.md
new file mode 100644
index 0000000..fe9c76a
--- /dev/null
+++ b/rfcs/NNN-form-associated-mixin.md
@@ -0,0 +1,412 @@
+---
+Status: Active
+Champions: "@justinfagnani"
+PR: https://github.com/lit/rfcs/pull/45
+---
+
+# Form Associated Mixin
+
+A new labs package with helpers for simplifying the creation of form-associated custom elements.
+
+## Objective
+
+Make it easier to build well-behaved and complete form-associated custom
+elements.
+
+### Goals
+
+- Reduced boilerplate needed for an element to participate in an HTML form,
+ including:
+ - Setting the formAssociated flag
+ - Managing the form value and form state
+ - Check and update element validity
+ - Implementing disable, reset, and restore behavior
+- Allow an element to have complete control over its public API.
+- Optionally, allow an element to opt-in to best-practice public APIs that match
+ built-in form element APIs.
+
+### Non-Goals
+
+- The `FormControl` mixin does not try to emulate the API of a built-in input
+ exactly, because native inputs are designed to allow for consumers to define
+ custom validation. `FormControl` is instead designed for the element author
+ to define validation.
+- This RFC does not aim to cover all form-related element use cases, such as
+ defining a form and managing form state. It it solely concerned with helpers
+ for defining individual form-associated elements, which could be used with
+ form management utilities.
+
+## Motivation
+
+Building a form-associated custom element requires a lot of common boilerplate:
+
+- Setting `static formAssociated = true`
+- Attaching `ElementInternals`
+- Setting a default ARIA role
+- Calling `internals.setFormValue()` when needed
+- Calling `internals.setValidity()` and `internals.checkValidity()` when needed
+- Handling `formDisabledCallback()`
+- Handling `formResetCallback()` and `formStateRestoreCallback()`
+- Requesting a reactive update when state held in internals changes.
+
+This boilplate makes it much harder to implement a form-associated element.
+Element authors should be able to focus on the actually necessary and unique
+parts of their element:
+
+- The element's value and state
+- Validation logic
+
+## Detailed Design
+
+A new `@lit-labs/forms` package will contain several utilities:
+- A `FormAssociated()` mixin that:
+ - Makes an element form associated
+ - Implements the standard form associated custom element callbacks
+ - Provides a validation hook
+ - Adds no public APIs to the element
+- A set of decorators for designating fields as the form value and state
+ - `@formValue()` for the public form value field
+ - `@formState()` for the fields that compose part of the form state
+ - `@formDefaultValue()` for the public form default value field
+ - `@formDefaultState()` for the public form default state field
+ - `@formStateGettter()` for private serializion of the form state
+ - `@formStateSetter()` for private deserializion of the form state
+- A `FormControll()` mixin that implements common public APIs for form controls,
+ like `.form`, `.disabled`, `.validity`, etc.
+
+### Why a mixin?
+
+`FormAssociated` needs to be a mixin because we need to implement the form
+associated callbacks on the element class. Controllers do not work for this use
+case.
+
+### Why decorators?
+
+A key choice in this design is to not require or add any _public_ API of the implementing element. Rather than automatically adding `.value` or `.disabled`
+fields, we leave that up to the element authors.
+
+In order for the mixin to function, it needs to perform some actions and read
+some element state when form-relate state changes. Decorators let us wrap
+arbitrary element accessors to invoke the mixin code paths to update form state,
+and let us communicate hooks to the mixin declaratively.
+
+```ts
+class MyFormElement extends FormAssociated(LitElement) {
+
+ // Setting this field calls internals.setFormValue()
+ @formValue()
+ @property({reflect: true})
+ accessor value: string = '';
+
+ // When the form resets, the value is set to this field
+ @formDefaultValue()
+ accessor defaultValue: string = '';
+
+ render() {
+ return html` @input=${this.#onInput}`;
+ }
+
+ #onInput(e) {
+ this.value = e.target.value;
+ }
+}
+```
+
+Because decorators are used to denote the important form fields, the name of the
+form value field does not need to be `value`. For instance, a checkbox element
+could use the field `checked`:
+
+
+```ts
+class MyCheckbox extends FormAssociated(LitElement) {
+
+ @formValue({
+ converter: {
+ toFormValue(value: boolean) {
+ return String(value);
+ },
+ fromFormValue(value: string) {
+ return Boolean(value);
+ },
+ })
+ @property({type: Boolean, reflect: true})
+ accessor checked: boolean = false;
+}
+```
+
+_Note on this example: "boolean" has to be repeated three times here. We can't
+deduplicate the TypeScript annotation and the type used by the `@property()`
+converter, but we might be able to come up with a way for `@formValue()` to
+automatically read the type from the `@property()` property options._
+
+### The FormAssociated mixin
+
+The `FormAssociated()` mixin implements most of the required logic for manging
+the form-related state on `ElementInternals`.
+
+#### formAssociated
+
+The `FormAssociated()` mixin defines `static formAssociated = true`
+
+#### ElementInternals
+
+The `FormAssociated()` mixin needs access to `ElementInternals`, so it must call
+`this.attachInternals()` in the constructor.
+
+Elements applying the `FormAssociated()` mixin and other mixins may also need
+access to internals, but `attachInternals()` is only allowed to be called once
+so that outside callers cannot access internals.
+
+We could vend a utility to get internals, but all mixins on an element would
+have to use that utility, creating either an incompatibility or an ad-hoc
+protocol.
+
+In order to be compatible with other code that calls `attachInternals()` a way
+that would be allowed locally, we can override `attachInternals()` to allow
+just one more call.
+
+
+
+ ##### Example `attachInternals()` override
+
+
+
+```ts
+const FormAssociated = (base) => class extends base {
+ #internals = super.attachInternals();
+ #attachInternalsCalled = false;
+
+ attachInternals() {
+ if (this.#attachInternalsCalled) {
+ throw new DOMException(
+ `Failed to execute 'attachInternals' on 'HTMLElement': ` +
+ `ElementInternals for the specified element was already attached.`,
+ 'NotSupportedError');
+ }
+ this.#attachInternalsCalled = true;
+ return this.#internals;
+ }
+}
+```
+
+