Browse Components
Validate
Shared form validation with accessible errors and native HTML rules.
Basic
Edit a field and leave it, or check the form, to see accessible inline errors. Nothing is sent.
View Code
<form class="stack" id="validate-basic" data-validate="check">
<p class="muted">
Fields marked * are required.
</p>
<p data-validate-summary role="status">
</p>
<div class="field">
<label for="contact-name">
Name
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="contact-name"
name="contact-name"
autocomplete="name"
required
>
</div>
<div class="field">
<label for="contact-email">
Email Address
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="contact-email"
name="contact-email"
type="email"
autocomplete="email"
required
aria-describedby="contact-email-help"
>
<p class="muted" id="contact-email-help">
Use an address where we can contact you.
</p>
</div>
<div class="cluster">
<button class="btn" type="submit">
Check Form
</button>
<button class="btn secondary" type="reset">
Reset
</button>
</div>
<p class="muted" role="status" data-validate-success hidden>
The form is valid. Nothing was sent.
</p>
</form>Complete form
A complete project brief using shared form components and HTML validation rules. No per-form JavaScript. This example uses data-validate="check" to validate and show confirmation without sending data.
View Code
<form
class="stack w-full max-w-form mx-auto gap-xl"
id="validate-complete"
data-validate="check"
>
<div class="stack gap-sm">
<h3 class="m-0">
Start a project
</h3>
<p class="muted m-0">
Tell us about your team and what you want to build. Fields marked * are required.
</p>
</div>
<p data-validate-summary="" role="status">
</p>
<fieldset class="stack form-section">
<legend>
Your Details
</legend>
<div class="grid items-start">
<div class="field">
<label for="project-name">
Full Name
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="project-name"
name="project-name"
autocomplete="name"
maxlength="100"
required=""
>
</div>
<div class="field">
<label for="project-email">
Work Email
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="project-email"
name="project-email"
type="email"
autocomplete="email"
required=""
>
</div>
</div>
</fieldset>
<fieldset class="stack form-section">
<legend>
Project Details
</legend>
<div class="grid items-start">
<div class="field">
<label for="project-type">
Project Type
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<div class="select-control">
<select id="project-type" name="type" required="">
<option value="">
Choose a project type
</option>
<option value="website">
Website
</option>
<option value="app">
Web application
</option>
<option value="mobile">
Mobile application
</option>
<option value="system">
Design system
</option>
</select>
</div>
</div>
<div class="field">
<label for="project-team">
Team Size
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<div class="number" data-number="">
<button
class="number-step"
type="button"
data-number-step="-1"
aria-label="Decrease Team Size"
aria-controls="project-team"
hidden=""
>
<svg
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5 12h14">
</path>
</svg>
</button>
<input
class="input"
id="project-team"
name="project-team"
type="number"
min="1"
max="100"
step="1"
value="1"
inputmode="numeric"
required=""
>
<button
class="number-step"
type="button"
data-number-step="1"
aria-label="Increase Team Size"
aria-controls="project-team"
hidden=""
>
<svg
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 5v14M5 12h14">
</path>
</svg>
</button>
</div>
</div>
<div class="field">
<label for="project-title">
Project Name
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="project-title"
name="project-title"
maxlength="120"
required=""
>
</div>
<div class="field">
<label for="project-start">
Start Date
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<div class="datepicker" data-datepicker="">
<input
class="input"
type="date"
id="project-start"
name="start"
required=""
aria-describedby="project-date-help"
>
<button
type="button"
data-datepicker-open=""
aria-label="Choose a date"
hidden=""
>
<span class="icon" aria-hidden="true" data-icon="calendar">
</span>
</button>
</div>
<p class="muted" id="project-date-help">
When would you like to get started?
</p>
</div>
</div>
<div class="field">
<label for="project-description">
Project Brief
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<textarea
class="input textarea"
id="project-description"
name="description"
rows="4"
required=""
minlength="20"
maxlength="2000"
aria-describedby="project-description-help"
data-error-too-short="Add at least 20 characters about your project."
>
</textarea>
<p class="muted" id="project-description-help">
Describe your goals and who you’re building for. At least 20 characters.
</p>
</div>
</fieldset>
<fieldset class="stack form-section">
<legend>
Preferences
</legend>
<label class="switch">
<input type="checkbox" role="switch" name="updates" value="email" checked="">
<span>
Email me project updates
</span>
</label>
<div class="field">
<label class="checkbox">
<input
type="checkbox"
name="consent"
value="yes"
required=""
data-error-value-missing="Confirm that the team can contact you about this project."
>
<span>
The team can contact me about this project
<span class="required-marker" aria-hidden="true">
*
</span>
</span>
</label>
</div>
</fieldset>
<div class="stack gap-md">
<div class="cluster">
<button class="btn" type="submit">
Send Project Brief
</button>
<button class="btn secondary" type="reset">
Reset
</button>
</div>
<p class="muted m-0">
Interactive demo only. Nothing is sent or saved.
</p>
<p role="status" data-validate-success="" hidden="" class="m-0 font-semibold">
Project brief ready. Demo only; nothing was sent.
</p>
</div>
</form>Native rules
Required choices, numeric limits and step increments use the same shared behavior.
View Code
<form class="stack" id="validate-rules" data-validate="check">
<p class="muted">
Fields marked * are required.
</p>
<p data-validate-summary role="status">
</p>
<div class="field">
<label for="seats">
Seats
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="seats"
name="seats"
type="number"
min="5"
max="50"
step="5"
required
aria-describedby="seats-help"
>
<p class="muted" id="seats-help">
Choose 5–50 seats in increments of 5.
</p>
</div>
<div class="field">
<label for="plan">
Plan
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<div class="select-control">
<select id="plan" name="plan" required>
<option value="">
Choose a plan
</option>
<option value="personal">
Personal
</option>
<option value="team">
Team
</option>
</select>
</div>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox" name="agreement" required>
<span>
I agree to the demo terms
<span class="required-marker" aria-hidden="true">
*
</span>
</span>
</label>
</div>
<div class="cluster">
<button class="btn" type="submit">
Check Form
</button>
<button class="btn secondary" type="reset">
Reset
</button>
</div>
<p class="muted" role="status" data-validate-success hidden>
The form is valid. Nothing was sent.
</p>
</form>Matching fields
Use data-match to require matching fields, such as email or password confirmation. No custom script is needed.
View Code
<form class="stack" id="validate-custom" data-validate="check">
<p class="muted">
Fields marked * are required.
</p>
<p data-validate-summary role="status">
</p>
<div class="field">
<label for="new-email">
Email Address
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="new-email"
name="new-email"
type="email"
required
autocomplete="email"
>
</div>
<div class="field">
<label for="confirm-email">
Confirm Email
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="confirm-email"
data-match="new-email"
data-error-match="Enter the same email address in both fields."
name="confirm-email"
type="email"
required
autocomplete="off"
>
</div>
<div class="cluster">
<button class="btn" type="submit">
Check Form
</button>
<button class="btn secondary" type="reset">
Reset
</button>
</div>
<p class="muted" role="status" data-validate-success hidden>
The form is valid. Nothing was sent.
</p>
</form>Server errors
Pass a field-name to message object to form.validate.setErrors. Errors appear beside the matching controls and focus moves to the first invalid field. This button simulates a server response; nothing is sent.
View Code
<form class="stack" id="validate-server" data-validate="check">
<p class="muted">
Fields marked * are required.
</p>
<p data-validate-summary role="status">
</p>
<div class="field">
<label for="username">
Username
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="username"
name="username"
required
pattern="[a-zA-Z0-9_]{3,20}"
aria-describedby="username-help"
>
<p class="muted" id="username-help">
Use 3–20 letters, numbers or underscores.
</p>
</div>
<button class="btn secondary" type="button" data-server>
Simulate Unavailable Name
</button>
<div class="cluster">
<button class="btn" type="submit">
Check Form
</button>
<button class="btn secondary" type="reset">
Reset
</button>
</div>
<p class="muted" role="status" data-validate-success hidden>
The form is valid. Nothing was sent.
</p>
</form>addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('validate-server');
form.querySelector('[data-server]').addEventListener('click', () => {
form.validate.setErrors({ username: 'This username is already taken.' });
});
});Right to left
Translate error copy and summary messages while keeping native constraints.
View Code
<form
class="stack"
id="validate-hebrew"
data-validate="check"
lang="he"
dir="rtl"
data-validate-summary="בדקו את השדות המסומנים ונסו שוב."
>
<p class="muted">
שדות המסומנים ב־* הם שדות חובה.
</p>
<p data-validate-summary role="status">
</p>
<div class="field">
<label for="he-name">
שם
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="he-name"
name="he-name"
required
autocomplete="name"
data-error-value-missing="הזינו את השם שלכם."
>
</div>
<div class="field">
<label for="he-email">
כתובת אימייל
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="he-email"
name="he-email"
required
type="email"
autocomplete="email"
data-error-value-missing="הזינו כתובת אימייל."
data-error-type-mismatch="הזינו כתובת אימייל תקינה."
>
</div>
<div class="cluster">
<button class="btn" type="submit">
בדיקת הטופס
</button>
<button class="btn secondary" type="reset">
איפוס
</button>
</div>
<p class="muted" role="status" data-validate-success hidden>
הטופס תקין. זוהי הדגמה בלבד; לא נשלח מידע.
</p>
</form>Enhanced inputs
Password, OTP, Number, Autocomplete and Textarea share error timing, correction and reset. All fields are required.
View Code
<form class="stack" id="validate-controls" data-validate="check">
<p class="muted">
Fields marked * are required.
</p>
<p data-validate-summary role="status">
</p>
<div class="field">
<label for="account-password">
Password
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<div class="password" data-password="">
<input
class="input"
id="account-password"
type="password"
name="password"
autocomplete="current-password"
autocapitalize="none"
spellcheck="false"
aria-describedby="password-help"
required=""
>
<button
type="button"
aria-controls="account-password"
aria-label="Show Password"
data-password-toggle=""
data-show="Show Password"
data-hide="Hide Password"
hidden=""
>
<svg
class="icon password-eye"
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z">
</path>
<circle cx="12" cy="12" r="2.5">
</circle>
</svg>
<svg
class="icon password-eye-off"
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m3 3 18 18M9 6.5A10 10 0 0 1 12 6c6 0 9.5 6 9.5 6a17 17 0 0 1-3 3.5M6 7.5A18 18 0 0 0 2.5 12s3.5 6 9.5 6a11 11 0 0 0 4-1M10.2 10.2a2.5 2.5 0 0 0 3.6 3.6">
</path>
</svg>
</button>
</div>
<p id="password-help" class="muted">
Use a sample password here. This example does not send or save it.
</p>
</div>
<div class="field">
<label for="verification-code">
Verification Code
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input otp"
data-otp=""
id="verification-code"
name="code"
type="text"
inputmode="numeric"
autocomplete="one-time-code"
maxlength="6"
pattern="[0-9]{6}"
required=""
spellcheck="false"
autocapitalize="off"
dir="ltr"
aria-describedby="verification-help"
>
<p class="muted" id="verification-help">
Enter the 6-digit code sent to your email.
</p>
</div>
<div class="field">
<label for="quantity">
Quantity
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<div class="number" data-number="">
<button
class="number-step"
type="button"
data-number-step="-1"
aria-label="Decrease Quantity"
aria-controls="quantity"
hidden=""
>
<svg
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5 12h14">
</path>
</svg>
</button>
<input
class="input"
id="quantity"
name="quantity"
type="number"
min="0"
max="20"
step="1"
inputmode="decimal"
required=""
>
<button
class="number-step"
type="button"
data-number-step="1"
aria-label="Increase Quantity"
aria-controls="quantity"
hidden=""
>
<svg
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 5v14M5 12h14">
</path>
</svg>
</button>
</div>
</div>
<div class="field">
<label for="destination">
Destination
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<input
class="input"
id="destination"
name="destination"
data-autocomplete=""
list="destinations"
placeholder="Search cities"
aria-describedby="destination-help"
required=""
>
<datalist id="destinations">
<option value="Amsterdam">
</option>
<option value="Barcelona">
</option>
<option value="Berlin">
</option>
<option value="Copenhagen">
</option>
<option value="Lisbon">
</option>
<option value="London">
</option>
<option value="Madrid">
</option>
<option value="Paris">
</option>
<option value="Rome">
</option>
<option value="Stockholm">
</option>
</datalist>
<p class="field-help" id="destination-help">
Choose a suggestion or enter another city.
</p>
</div>
<div class="field">
<label for="project-notes">
Project Notes
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<textarea
class="input textarea"
id="project-notes"
name="notes"
rows="4"
aria-describedby="notes-help"
placeholder="What should the team know?"
required=""
>
</textarea>
<p id="notes-help" class="muted">
Include goals, context, and any important details.
</p>
</div>
<div class="cluster">
<button class="btn" type="submit">
Check Form
</button>
<button class="btn secondary" type="reset">
Reset
</button>
</div>
<p role="status" class="muted" data-validate-success hidden>
The form is valid. Nothing was sent.
</p>
</form>Choices and slider
Required choices and a slider with a native minimum of 25%. Each control uses the shared validation behavior.
View Code
<form class="stack" id="validate-choices" data-validate="check" "">
<p class="muted">
Fields marked * are required.
</p>
<p data-validate-summary="" role="status">
</p>
<fieldset class="radio-group">
<legend>
Delivery Frequency
<span class="required-marker" aria-hidden="true">
*
</span>
</legend>
<label class="radio">
<input type="radio" name="frequency" value="daily" required="">
<span>
Daily
</span>
</label>
<label class="radio">
<input type="radio" name="frequency" value="weekly" required="">
<span>
Weekly
</span>
</label>
<label class="radio">
<input type="radio" name="frequency" value="monthly" required="">
<span>
Monthly
</span>
</label>
</fieldset>
<fieldset class="segmented">
<legend>
Billing Period
<span class="required-marker" aria-hidden="true">
*
</span>
</legend>
<div class="segmented-options">
<label>
<input type="radio" name="billing-period" value="monthly" required="">
<span>
Monthly
</span>
</label>
<label>
<input type="radio" name="billing-period" value="yearly" required="">
<span>
Yearly
</span>
</label>
</div>
</fieldset>
<label class="checkbox">
<input type="checkbox" name="updates" value="email" required="">
<span>
I agree to the demo terms
<span class="required-marker" aria-hidden="true">
*
</span>
</span>
</label>
<label class="switch">
<input type="checkbox" role="switch" name="notifications" value="enabled" required="">
<span>
Enable account protection
<span class="required-marker" aria-hidden="true">
*
</span>
</span>
</label>
<div class="slider-field">
<div class="slider-heading">
<label for="volume">
Volume
</label>
<output for="volume" data-unit="%">
50%
</output>
</div>
<input id="volume" name="volume" type="range" min="25" max="100" step="1" value="50">
</div>
<div class="cluster">
<button class="btn" type="submit">
Check Form
</button>
<button class="btn secondary" type="reset">
Reset
</button>
</div>
<p role="status" class="muted" data-validate-success hidden="">
The form is valid. Nothing was sent.
</p>
</form>Pickers and upload
Date range and compact Color Picker focus their visible controls. Upload rejection messages participate in validation. All fields are required.
View Code
<form class="stack" id="validate-pickers" data-validate="check">
<p class="muted">
Fields marked * are required.
</p>
<p data-validate-summary role="status">
</p>
<fieldset class="stack" data-date-range="">
<legend>
Date Range
<span class="required-marker" aria-hidden="true">
*
</span>
</legend>
<div class="datepicker-fields">
<div class="field">
<label for="start-date">
Start Date
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<div class="datepicker" data-datepicker="">
<input
class="input"
type="date"
data-range-start=""
id="start-date"
name="start"
aria-describedby="date-help"
required=""
>
<button
type="button"
data-datepicker-open=""
aria-label="Choose a date"
hidden=""
>
<svg
class="icon"
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="3" y="5" width="18" height="16" rx="2">
</rect>
<path d="M7 3v4m10-4v4M3 10h18M7 14h2m4 0h2m-8 3h2">
</path>
</svg>
</button>
</div>
<p class="muted" id="date-help">
Choose a start date in any month or year.
</p>
</div>
<div class="field">
<label for="end-date">
End Date
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<div class="datepicker" data-datepicker="">
<input
class="input"
type="date"
data-range-end=""
id="end-date"
name="end"
aria-describedby="end-help range-error"
required=""
>
<button
type="button"
data-datepicker-open=""
aria-label="Choose a date"
hidden=""
>
<svg
class="icon"
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="3" y="5" width="18" height="16" rx="2">
</rect>
<path d="M7 3v4m10-4v4M3 10h18M7 14h2m4 0h2m-8 3h2">
</path>
</svg>
</button>
</div>
<p class="muted" id="end-help">
Choose an end date on or after the start date.
</p>
</div>
</div>
<p id="range-error" data-range-error="" aria-live="polite">
</p>
</fieldset>
<div class="field">
<label for="brand-color">
Brand Color
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<div class="color-picker" data-color-picker="" data-swatch-only>
<button
type="button"
data-color-swatch=""
aria-label="Choose Brand Color"
hidden=""
>
</button>
<input
class="input"
dir="ltr"
id="brand-color"
name="brand"
pattern="#[0-9a-fA-F]{6}"
maxlength="7"
spellcheck="false"
autocomplete="off"
required=""
aria-describedby="brand-color-help"
>
</div>
<p class="muted" id="brand-color-help">
Choose a color or enter a six-digit hex value, such as #294cda.
</p>
</div>
<div class="file-upload" data-file-upload="" data-max-size="5242880" data-max-files="5">
<div class="upload-dropzone">
<svg
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
stroke-linejoin="round"
class="icon"
aria-hidden="true"
focusable="false"
>
<path d="M12 15V3m-5 5 5-5 5 5M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3">
</path>
</svg>
<label for="attachments">
Choose Files or Drop Them Here
<span class="required-marker" aria-hidden="true">
*
</span>
</label>
<p id="upload-help">
PDF, PNG, or JPEG. Up to 5 files, 5 MB each.
</p>
<input
id="attachments"
name="attachments"
type="file"
accept=".pdf,image/png,image/jpeg"
multiple=""
aria-describedby="upload-help"
required=""
>
</div>
</div>
<div class="cluster">
<button class="btn" type="submit">
Check Form
</button>
<button class="btn secondary" type="reset">
Reset
</button>
</div>
<p role="status" class="muted" data-validate-success hidden>
The form is valid. Nothing was sent.
</p>
</form>Installation
Download these files into the matching paths in your project, or use Create to assemble a kit. Include each dependency once. Do not also load app.css.
<link rel="stylesheet" href="/library/assets/base.css">
<link rel="stylesheet" href="/library/assets/field.css">
<link rel="stylesheet" href="/library/assets/button.css">
<link rel="stylesheet" href="/library/assets/validate.css">
<link rel="stylesheet" href="/library/assets/icon.css">
<script defer src="/library/assets/validate.js"></script>
<script defer src="/library/assets/icon.js"></script>
<script defer src="/library/icons/validate.js"></script>22,915 bytes CSS including base · 13,799 bytes runtime JavaScript
Usage
HTML-only setup
Load the shared assets once. Add data-validate to a form with action and method for normal submission, or data-validate="check" to validate without submitting. Add a hidden element with data-validate-success for local confirmation; the component shows it on a valid check and hides it on edits, invalid checks and reset. This confirms validity, not server delivery. No per-form script is needed.
Choose your rules
Use required, type="email", type="url", min, max, step, minlength, maxlength and pattern. Use data-match="other-field-id" for confirmation fields in the same form and data-error-match for the message. Optional empty fields remain valid unless their rules require otherwise.
Submission and custom logic
Normal data-validate forms submit natively once valid. Listen to cancelable validate:valid and preventDefault only when your app needs custom submission. Check mode never submits. A canceled validate:valid event suppresses local confirmation. Client-side validation does not replace server validation.
Native foundation
Use required, type, min, max, step, minlength, maxlength and pattern on native controls. Group each label/control/help in .field. Without JavaScript, native browser validation remains available. Client validation does not replace server validation.
Server responses
Call form.validate.setErrors({ username: "This username is already taken." }) with native field names as keys and strings as messages. Each call replaces prior server errors; an empty object clears them without clearing native rules. Errors use the existing inline feedback, radio/date-range grouping and visible focus targets. Focus moves to the first applicable field in form order; pass {focus:false} as the second argument to keep focus. Unknown names or non-string messages throw before changing errors. Disabled, readonly and ignored controls do not show errors. Editing clears the affected server error; reset clears all feedback. Your application owns requests and stale-response handling.
Custom rules
Use layoutValidate.addRule(control,(value,control,form)=>message) for synchronous rules. Return an empty string when valid. The existing layoutValidate.setError(control,message) API remains available for individual controls; layoutValidate.setErrors(form,errors,options) is the form-level alternative.
Localization
The default messages use the browser locale. Override with data-error or a constraint-specific attribute such as data-error-value-missing. Set the form data-validate-summary text for a translated summary. Hebrew RTL is demonstrated above.
Lifecycle
Use layoutValidate.refresh(root) after mounting and destroy(form) before removal. validate(form,{focus:false}) performs an explicit validation without moving focus. Native form reset clears presented errors. Programmatic value updates should dispatch input when live feedback is needed. After enhancement, form.validate exposes the same form-scoped API. Destroy restores any prior validate property.
Composite controls
Password, OTP, Number, Autocomplete, Select, Date Picker, Color Picker, Upload, Textarea, Checkbox, Radio, Switch, Segmented and Slider use their native form value source. Date ranges and compact colors provide visible validation targets. Sliders always have a native value; use a custom rule for application-specific requirements. Upload rejects invalid files and reports their rejection through native custom validity until a new selection, removal or reset.
Feedback ownership
Within enhanced forms, shared validation owns inline errors. Auxiliary picker and search inputs are excluded. data-validate-target points to a visible proxy by ID; data-validate-field marks its visible error container. These hooks also support custom composite controls.
Selective installation
Include the shared Validate assets plus the assets for controls used in your form. Selecting Validate in Create does not bundle every form component.
Building with AI? The AI Manifest has these notes and the full contract. See the AI Guide.