Working with Forms
Espalier form controls participate in the browser's native form lifecycle through ElementInternals. The core pattern is:
- Use
<esp-form>as the form wrapper. - Wrap each control in
<esp-form-item>to get labels and inline validation messaging. - 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
namedirectly on the control. - Set
field-nameonesp-form-itemand let it propagate thenameattribute 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-messageor other component-specific validation messaging props when you want custom text. - On submit,
esp-formvalidates 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-submitesp-submit-responseesp-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
- Read Getting Started for the initial install and import story.
- Read Browser Support before depending on newer browser APIs.
- Inspect the component docs for Form, Form Item, Input, and the picker components for API-level details.