Working with Forms

Espalier form controls participate in the browser's native form lifecycle through ElementInternals. The core pattern is:

  1. Use <esp-form> as the form wrapper.
  2. Wrap each control in <esp-form-item> to get labels and inline validation messaging.
  3. Use form-associated Espalier controls such as esp-input, esp-textarea, esp-checkbox, esp-switch, esp-radio-button, and the picker components.

Because esp-form renders a real light-DOM <form>, you still get the browser's submission, reset, and accessibility behavior. Espalier adds better labels, error handling, theming, and fetch-based submission when you opt into it.

Basic Structure

esp-form does not provide layout by itself. Pair it with esp-box, grid, or your own CSS:

<esp-form action="/api/profile" method="post">
  <esp-box>
    <esp-form-item label="First name" field-name="firstName">
      <esp-input required></esp-input>
    </esp-form-item>

    <esp-form-item label="Email address" field-name="email">
      <esp-input input-type="email" required></esp-input>
    </esp-form-item>

    <esp-button button-type="submit" label="Save"></esp-button>
  </esp-box>
</esp-form>

Naming Fields

Your form controls need a field name to participate in submission. You can provide that in either of two ways:

  • Set name directly on the control.
  • Set field-name on esp-form-item and let it propagate the name attribute to the slotted control.

Using field-name often keeps the label and submitted field name together:

<esp-form-item label="Phone number" field-name="phone">
  <esp-input input-type="tel" tel-localities="US CA GB"></esp-input>
</esp-form-item>

Validation

Espalier controls support native-style constraint validation plus inline error presentation through esp-form-item.

  • Use required, min, max, step, and other normal constraint attributes on the control.
  • Use required-message or other component-specific validation messaging props when you want custom text.
  • On submit, esp-form validates the controls, focuses the first invalid field, and prevents submission until the errors are resolved.
<esp-form-item label="Password" field-name="password">
  <esp-input
    input-type="password"
    required
    required-message="Please enter a password to continue.">
  </esp-input>
</esp-form-item>

Standard Submit, Reset, And Buttons

Espalier buttons use the button-type attribute instead of the native type attribute:

<div style="display:flex; gap: var(--esp-size-small);">
  <esp-button button-type="submit" label="Submit"></esp-button>
  <esp-button button-type="reset" label="Reset" variant="danger"></esp-button>
</div>

Without use-fetch, esp-form behaves like a native form and submits to action using the configured method.

Fetch Submission

If you add use-fetch, Espalier intercepts the submit, performs a fetch(), and dispatches lifecycle events:

  • esp-submit
  • esp-submit-response
  • esp-submit-error

Add use-json when the server expects JSON instead of FormData.

<esp-form
  action="/api/preferences"
  use-fetch
  use-json
  label="Preferences">
  <esp-box>
    <esp-form-item label="Display name" field-name="displayName">
      <esp-input required></esp-input>
    </esp-form-item>

    <esp-form-item label="Timezone" field-name="timezone">
      <esp-pick-one required placeholder="Choose a timezone">
        <esp-picker-item text="Pacific Time" value="America/Los_Angeles"></esp-picker-item>
        <esp-picker-item text="Mountain Time" value="America/Denver"></esp-picker-item>
        <esp-picker-item text="Central Time" value="America/Chicago"></esp-picker-item>
        <esp-picker-item text="Eastern Time" value="America/New_York"></esp-picker-item>
      </esp-pick-one>
    </esp-form-item>

    <esp-button button-type="submit" label="Save preferences"></esp-button>
  </esp-box>
</esp-form>
<script>
  const form = findByTagName("esp-form")[0];
  form.addEventListener("esp-submit-response", (ev) => {
    console.log("Saved:", ev.detail.ok);
  });
  form.addEventListener("esp-submit-error", (ev) => {
    console.error("Request failed:", ev.detail.error);
  });
</script>

Dialog Forms

When method="dialog", submitting the form dispatches closeDialog, which esp-dialog listens for. This is useful for confirmation dialogs and quick-edit flows:

<esp-dialog>
  <esp-box>
    <h2>Rename project</h2>
    <esp-form method="dialog" label="Rename project">
      <esp-form-item label="Project name" field-name="name">
        <esp-input required></esp-input>
      </esp-form-item>
      <div style="display:flex; gap: var(--esp-size-small);">
        <esp-button button-type="submit" label="Save"></esp-button>
        <esp-button button-type="reset" label="Reset" variant="danger"></esp-button>
      </div>
    </esp-form>
  </esp-box>
</esp-dialog>

A Full Example

<esp-form action="/api/signup" use-fetch use-json label="Sign up">
  <esp-box>
    <esp-form-item label="Full name" field-name="fullName">
      <esp-input required></esp-input>
    </esp-form-item>

    <esp-form-item label="Email address" field-name="email">
      <esp-input input-type="email" required></esp-input>
    </esp-form-item>

    <esp-form-item label="Favorite color" field-name="favoriteColor">
      <esp-pick-one required placeholder="Pick one">
        <esp-picker-item text="Red" value="red"></esp-picker-item>
        <esp-picker-item text="Green" value="green"></esp-picker-item>
        <esp-picker-item text="Blue" value="blue"></esp-picker-item>
      </esp-pick-one>
    </esp-form-item>

    <esp-form-item label="About you" field-name="bio">
      <esp-textarea rows="4" placeholder="Tell us a little about yourself"></esp-textarea>
    </esp-form-item>

    <esp-form-item label="I agree to the terms" field-name="terms">
      <esp-checkbox value="agreed" required>
        Yes, I agree
      </esp-checkbox>
    </esp-form-item>

    <div style="display:flex; gap: var(--esp-size-small);">
      <esp-button button-type="submit" label="Create account"></esp-button>
      <esp-button button-type="reset" label="Reset" variant="danger"></esp-button>
    </div>
  </esp-box>
</esp-form>

Next Steps

Where does this show?