Kin Form
Docs
INTRODUCTION
Getting Started Architecture

EXAMPLES
Basic Example Any-level Validation Dependent Field Validation Flexible Form Structures Flexible Field Components Array

API

Architecture

Overview

Kin Form is built on a hierarchical and compositional architecture, making it flexible enough to handle everything from simple inputs to complex, deeply nested forms.

The architecture consists of three main components:

  • FormField: The base class for all individual form fields (e.g., a text input, a checkbox, a select dropdown). It manages the value, validation, and status (touched, invalid, etc.) of a single piece of data.

  • FieldGroup: A special type of field that contains other fields. It allows you to group fields together into a nested object structure. It aggregates the status of its children; for example, a FieldGroup is considered invalid if any of its child fields are invalid.

  • FormController: The main controller that manages the entire form. It is a FieldGroup with additional logic for handling form submission, reset, and top-level state like dirty and submitting.

This structure allows you to compose complex forms by nesting FormField and FieldGroup components.

Architecture

How It Works in Practice

You typically instantiate a FormController in your Lit component. The controller then uses the field() directive to link parts of its data model to FormField or FieldGroup elements in your template.

interface Model {
  email: string;
  password: string;
}

class LoginPage extends LitElement {
  // The controller manages the form's state.
  #form = new FormController<Model>(this, {
    initialValue: { email: "", password: "" },
    onSubmit: (form) => {
      console.log("Form submitted!", form.value);
    },
  });

  protected override render(): unknown {
    const { field, handleSubmit } = this.#form;
    return html`
      <!-- The field() directive links these fields to the controller -->
      <text-field ${field("email")}></text-field>
      <password-field ${field("password")}></password-field>

      <button @click=${handleSubmit}>Log in</button>
    `;
  }
}

Design Rationale

In Kin Form, FormController is implemented as a Lit ReactiveController, but it is also technically a custom element. This design was chosen to reduce boilerplate and improve developer ergonomics.

An alternative approach would be to use it as a traditional custom element, like <kin-form>.

// Alternative, more verbose approach
class LoginPage extends LitElement {
  protected override render(): unknown {
    return html`
      <kin-form
        .initialValue=${...}
        .onSubmit=${...}
        .template=${({field, handleSubmit}) => html`
          <text-field ${field('email')}></text-field>
          <button @click=${handleSubmit}>Log in</button>
        `}
      ></kin-form>
    `;
  }
}

This alternative is slightly more verbose and can be less efficient, as the template function may be recreated on each render. The current approach, using a ReactiveController, provides a more direct and streamlined way to manage form state within a Lit component.

On this page

Overview How It Works in Practice Design Rationale
Created with ♥️ by  Man Hoang (Kin)