Form
Provides a basic way to validate your form and show feedback to your users with native HTML5 form validation. Use attributes such as pattern to add other validation rules.
novalidate must be present on form to handle validation with JavaScript.
Installation
Follow instructions for individual framework usage below
Scripts
The Form script handles custom validation on submit and can set data-loading on the submitter after a valid submit.
INFO
Native validation is enough for most forms. Validation libraries are often unnecessary abstractions and are only needed for very complex forms. In those cases, we recommend VeeValidate or TanStack Form.
Examples
Form
<form class="x-form grid grid-cols-12 gap-4" is="x-form">
<x-field class="x-field col-span-4">
<label class="x-label" for="given-name">First name</label>
<x-control class="x-control">
<input id="given-name" name="given-name" autocomplete="given-name" required>
</x-control>
</x-field>
<x-field class="x-field col-span-4">
<label class="x-label" for="family-name">Last name</label>
<x-control class="x-control">
<input id="family-name" name="family-name" autocomplete="family-name" required>
</x-control>
</x-field>
<x-field class="x-field col-span-4">
<label class="x-label" for="email">Email</label>
<x-control class="x-control">
<input type="email" id="email" name="email" autocomplete="email" required>
</x-control>
</x-field>
<x-field class="x-field col-span-4 items-start">
<button class="x-button" type="submit">Submit</button>
</x-field>
</form><form class="x-form grid grid-cols-12 gap-4" data-controller="x-form">
<div data-controller="x-field" class="x-field col-span-4">
<label class="x-label" for="given-name">First name</label>
<div data-controller="x-control" class="x-control">
<input id="given-name" name="given-name" autocomplete="given-name" required>
</div>
</div>
<div data-controller="x-field" class="x-field col-span-4">
<label class="x-label" for="family-name">Last name</label>
<div data-controller="x-control" class="x-control">
<input id="family-name" name="family-name" autocomplete="family-name" required>
</div>
</div>
<div data-controller="x-field" class="x-field col-span-4">
<label class="x-label" for="email">Email</label>
<div data-controller="x-control" class="x-control">
<input type="email" id="email" name="email" autocomplete="email" required>
</div>
</div>
<div data-controller="x-field" class="x-field col-span-4 items-start">
<button class="x-button" type="submit">Submit</button>
</div>
</form><script setup lang="ts">
import { Form } from '@/components/form'
import { Field } from '@/components/field'
import { Label } from '@/components/label'
import { Control } from '@/components/control'
import { Button } from '@/components/button'
</script>
<template>
<Form class="grid grid-cols-12 gap-4">
<Field class="col-span-4">
<Label for="given-name">First name</Label>
<Control>
<input id="given-name" name="given-name" autocomplete="given-name" required>
</Control>
</Field>
<Field class="col-span-4">
<Label for="family-name">Last name</Label>
<Control>
<input id="family-name" name="family-name" autocomplete="family-name" required>
</Control>
</Field>
<Field class="col-span-4">
<Label for="email">Email</Label>
<Control>
<input type="email" id="email" name="email" autocomplete="email" required>
</Control>
</Field>
<Field class="col-span-4 items-start">
<Button type="submit">Submit</Button>
</Field>
</Form>
</template>import { Form } from '@/components/form'
import { Field } from '@/components/field'
import { Label } from '@/components/label'
import { Control } from '@/components/control'
import { Button } from '@/components/button'
export function Example() {
return (
<Form className="grid grid-cols-12 gap-4">
<Field className="col-span-4">
<Label htmlFor="given-name">First name</Label>
<Control>
<input id="given-name" name="given-name" autoComplete="given-name" required />
</Control>
</Field>
<Field className="col-span-4">
<Label htmlFor="family-name">Last name</Label>
<Control>
<input id="family-name" name="family-name" autoComplete="family-name" required />
</Control>
</Field>
<Field className="col-span-4">
<Label htmlFor="email">Email</Label>
<Control>
<input type="email" id="email" name="email" autoComplete="email" required />
</Control>
</Field>
<Field className="col-span-4 items-start">
<Button type="submit">Submit</Button>
</Field>
</Form>
)
}JavaScript API
validateForm
- Type:
(event: Event | SubmitEvent, options?: ValidateFormOptions) => void - Kind:
sync
Validates a form with checkValidity and runs validateField on every field. On invalid submit it scrolls to and focuses the first invalid element, on valid submit it marks the submitter with a loading attribute.
ValidateFormOptions
validateSelector
- Type:
string - Default:
.x-field
Selector of field wrappers which will be validated.
validateOptions
- Type:
ValidateFieldOptions - Default:
{}
Additional options for validateField
validateField
- Type:
typeof validateField - Default:
validateField
Override the field validation function.
submitterLoadingAttribute
- Type:
string - Default:
data-loading
Loading attribute that will be added to the submitter element (e.g. a button) on valid submit.
scrollOptions
- Type:
ScrollIntoViewOptions - Default:
{ behavior: 'smooth', block: 'center' }
Scroll options when scrolling to an invalid element.
validateField
- Type:
(element: HTMLElement, options?: ValidateFieldOptions) => void - Kind:
sync
Validates a single field wrapper (typically .x-field). It appends a validation info message (with data-validity) to the field and a validation icon inside .x-control. The message text is resolved from options.validationMessage, the data-validation-message attribute on the invalid element, or the native validationMessage.
ValidateFieldOptions
validationMessage
- Type:
string - Default:
undefined
Overrides the displayed validation message.
selector
- Type:
string - Default:
:is(input:not([type="hidden"]), textarea, select):not([readonly], [data-novalidate])
Selector of elements inside the field that are validated. Add data-novalidate or readonly to skip validation.
validitySelector
- Type:
string - Default:
[data-validity]
Selector for dynamically added content in the DOM, such as the info message or icon — it is removed on re-validation.
infoContent
- Type:
string - Default:
<div class="x-info text-error" data-validity></div>
Element appended to the field with the validation message.
iconParentSelector
- Type:
string - Default:
.x-control
Element the validation icon is appended into.
iconSelector
- Type:
string - Default:
.ms-auto
iconContent
- Type:
string - Default:
<div class="ms-auto"></div>
Wrapper element for the validation icon, created inside iconParentSelector when missing.
validIcon
- Type:
string | null - Default:
null
invalidIcon
- Type:
string - Default:
<svg class="text-error" data-validity aria-hidden="true"><use href="#heroicons-outline/exclamation-circle"></use></svg>