Form
Form wrapper with built-in validation, error aggregation, and submit handling. Composes Input, Textarea, and Button into a cohesive data-entry workflow.
<arc-form> Overview
Form is the top-level container that turns a collection of ARC UI input components into a coordinated, validatable unit. It intercepts the native submit event, runs constraint validation across every child field, surfaces per-field error messages, and emits a structured arc-submit event only when all rules pass.
Use Form whenever you collect more than a single field from the user — contact forms, login screens, settings panels, multi-step wizards. Wrapping fields in a Form gives you automatic required-field enforcement, pattern matching, and a consistent error-summary experience without writing imperative validation logic.
Validation comes from the controls. Form does not re-derive whether a field is filled; it calls each control's checkValidity() and reads its validationMessage. So a control that understands its own emptiness — a multi-select with an empty array, a date range with one end set — is judged on its own terms, and a control you have written yourself participates as long as it is form-associated. Form only clears error text it wrote, so an error you set from a server response survives a later submit attempt.
Fields can sit anywhere inside the form. Nesting a control inside Fieldset, Card, or any layout component makes no difference to whether it is found, validated, serialized, or disabled along with the form.
submit() is for the submit control the form cannot own. A button outside the <arc-form> does not trigger it — a wizard's Next in a parent toolbar, a Save in an app-level header, a keyboard shortcut. Calling submit() runs validation and fires arc-submit cancelably, exactly as pressing a submit button inside the form does, and it routes through the real <form> so action-mode submits still navigate. It is not a way to skip validation.
reset() restores, it does not empty. Each control returns to the state it had when it first connected, which is what reset means in HTML — a field that shipped with a default value gets that value back, rather than being blanked.
All ARC UI form controls (Input, Textarea, Select, Checkbox, Toggle, RadioGroup) implement the ElementInternals form-association API, so they participate in native FormData collection automatically. This means you can use them inside a plain <form action="/api/contact" method="POST"> for zero-JS static site submissions, or wrap them in <arc-form> for the full JS validation + arc-submit experience. For completely framework-free sites, ARC UI ships a form.css stylesheet that applies the same design tokens to native HTML form elements.
Guidelines
When to use
- Wrap all related fields inside a single Form so validation and submission are coordinated
- Give every field a unique `name` so FormData serialisation produces the correct key-value pairs
- Set `required` on mandatory fields and let Form handle the validation messaging
- Provide a clear submit Button with `type="submit"` as the last child of the Form
- Use the `loading` prop to indicate an async submission in progress and prevent duplicate requests
- Listen for `arc-submit` instead of native `submit` to receive validated, serialized data
- Include meaningful labels on every field so the error summary is readable
When not to use
- Do not nest one Form inside another — HTML forbids nested forms and behavior is undefined
- Do not handle validation manually when the built-in constraint API already covers your rules
- Do not rely solely on client-side validation — always validate on the server as well
- Do not place the submit Button outside the Form; it will not trigger submission
- Avoid calling `event.preventDefault()` on `arc-submit` unless you need to cancel the submission
- Do not use `novalidate` as a permanent workaround for broken validation — fix the constraints instead
Features
- Intercepts native form submission and runs constraint validation on all associated fields
- Aggregates per-field errors and displays an optional error summary above the submit button
- Fires `arc-submit` with a serialized FormData payload only when validation passes
- Supports `novalidate` to bypass built-in checks for custom validation flows
- Coordinates `disabled` state — disabling the form disables every child field
- Works with any form-associated element, including native inputs and ARC UI components
- Prevents double-submission by disabling the submit button while `loading` is true
- Reset support via `arc-reset` event and programmatic `.reset()` method — restores initial values rather than blanking fields
- `submit()` submits from outside the form — a toolbar button or shortcut — with validation and `arc-submit` unchanged
- Finds controls at any depth, including inside Fieldset and layout components
- Delegates validity to each control, so custom form-associated elements participate
- Keyboard-accessible — Enter key inside a single-line input triggers submission
- Pairs with Input, Textarea, Select, Checkbox, and RadioGroup without extra wiring
Preview
Usage
This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.
<arc-form>
<div style="display:flex; flex-direction:column; gap:16px; width:100%; max-width:480px;">
<arc-input label="Name" name="name" placeholder="Jane Doe" required></arc-input>
<arc-input label="Email" name="email" type="email" placeholder="[email protected]" required></arc-input>
<arc-textarea label="Message" name="message" rows="4" placeholder="How can we help?" required></arc-textarea>
<arc-button variant="primary" type="submit">Send message</arc-button>
</div>
</arc-form>
<script>
document.querySelector('arc-form')
.addEventListener('arc-submit', (e) => {
console.log('Form data:', Object.fromEntries(e.detail.formData));
});
</script> import { Button, Form, Input, Textarea } from '@arclux/arc-ui-react';
function ContactForm() {
const handleSubmit = (e: CustomEvent) => {
const data = Object.fromEntries(e.detail.formData);
console.log('Form data:', data);
};
return (
<Form onArcSubmit={handleSubmit}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: '100%', maxWidth: 480 }}>
<Input label="Name" name="name" placeholder="Jane Doe" required />
<Input label="Email" name="email" type="email" placeholder="[email protected]" required />
<Textarea label="Message" name="message" rows={4} placeholder="How can we help?" required />
<Button variant="primary" type="submit">Send message</Button>
</div>
</Form>
);
} <script setup>
import { Button, Form, Input, Textarea } from '@arclux/arc-ui-vue';
function handleSubmit(e) {
const data = Object.fromEntries(e.detail.formData);
console.log('Form data:', data);
}
</script>
<template>
<Form @arc-submit="handleSubmit">
<div style="display:flex; flex-direction:column; gap:16px; width:100%; max-width:480px;">
<Input label="Name" name="name" placeholder="Jane Doe" required />
<Input label="Email" name="email" type="email" placeholder="[email protected]" required />
<Textarea label="Message" name="message" rows="4" placeholder="How can we help?" required />
<Button variant="primary" type="submit">Send message</Button>
</div>
</Form>
</template> <script>
import { Button, Form, Input, Textarea } from '@arclux/arc-ui-svelte';
function handleSubmit(e) {
const data = Object.fromEntries(e.detail.formData);
console.log('Form data:', data);
}
</script>
<Form on:arc-submit={handleSubmit}>
<div style="display:flex; flex-direction:column; gap:16px; width:100%; max-width:480px;">
<Input label="Name" name="name" placeholder="Jane Doe" required />
<Input label="Email" name="email" type="email" placeholder="[email protected]" required />
<Textarea label="Message" name="message" rows="4" placeholder="How can we help?" required />
<Button variant="primary" type="submit">Send message</Button>
</div>
</Form> import { Component } from '@angular/core';
import { Button, Form, Input, Textarea } from '@arclux/arc-ui-angular';
@Component({
imports: [Button, Form, Input, Textarea],
template: `
<arc-form (arc-submit)="handleSubmit($event)">
<div style="display:flex; flex-direction:column; gap:16px; width:100%; max-width:480px;">
<arc-input label="Name" name="name" placeholder="Jane Doe" required></arc-input>
<arc-input label="Email" name="email" type="email" placeholder="[email protected]" required></arc-input>
<arc-textarea label="Message" name="message" rows="4" placeholder="How can we help?" required></arc-textarea>
<arc-button variant="primary" type="submit">Send message</arc-button>
</div>
</arc-form>
`,
})
export class ContactFormComponent {
handleSubmit(e: CustomEvent) {
const data = Object.fromEntries(e.detail.formData);
console.log('Form data:', data);
}
} import { Button, Form, Input, Textarea } from '@arclux/arc-ui-solid';
function ContactForm() {
const handleSubmit = (e: CustomEvent) => {
const data = Object.fromEntries(e.detail.formData);
console.log('Form data:', data);
};
return (
<Form onArcSubmit={handleSubmit}>
<div style={{ display: 'flex', 'flex-direction': 'column', gap: '16px', width: '100%', 'max-width': '480px' }}>
<Input label="Name" name="name" placeholder="Jane Doe" required />
<Input label="Email" name="email" type="email" placeholder="[email protected]" required />
<Textarea label="Message" name="message" rows={4} placeholder="How can we help?" required />
<Button variant="primary" type="submit">Send message</Button>
</div>
</Form>
);
} import { Button, Form, Input, Textarea } from '@arclux/arc-ui-preact';
function ContactForm() {
const handleSubmit = (e: CustomEvent) => {
const data = Object.fromEntries(e.detail.formData);
console.log('Form data:', data);
};
return (
<Form onArcSubmit={handleSubmit}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: '100%', maxWidth: 480 }}>
<Input label="Name" name="name" placeholder="Jane Doe" required />
<Input label="Email" name="email" type="email" placeholder="[email protected]" required />
<Textarea label="Message" name="message" rows={4} placeholder="How can we help?" required />
<Button variant="primary" type="submit">Send message</Button>
</div>
</Form>
);
} API
-
actionstring'' - Form action URL for native form submission. When set, the form submits to this URL using the browser's built-in mechanism.
-
methodstring'' - HTTP method for native form submission (GET or POST). Only applies when action is set.
-
disabledbooleanfalse - Disables the entire form, propagating the disabled state to every child field. Useful for read-only previews or while awaiting permissions.
-
novalidatebooleanfalse - When true, skips built-in constraint validation on submit. Use this when you need to implement a fully custom validation flow while still leveraging Form for data serialisation.
-
loadingbooleanfalse - Indicates an asynchronous submission is in progress. Disables the submit button and shows a loading indicator to prevent duplicate requests.
-
errorSummarybooleantrue - When true, renders an aggregated list of validation errors above the submit area after a failed submission attempt. Set to false to handle error display manually.
Methods
-
submit() - Submit the form as though its submit button had been pressed: validation
runs, and
arc-submitfires cancelably. The path for a submit control the form cannot own — a wizard's "Next" in a parent toolbar, a keyboard shortcut — since a button outside the form does not trigger it. Routed through the real<form>so action-mode submits still navigate. -
reset() - Reset every child control to the state it had when it first connected,
and clear error display.
Delegates to each control's formResetCallback — the same path a native
form.reset() takes, which never reaches these controls because they live
in this element's light DOM rather than inside the shadow <form>. The form
used to blank them instead (
value = '',checked = false), which is not what reset means: a control that shipped with a default lost it, and a non-string value (a multi-select's array, a date range's object) was left untouched entirely.
Events
-
arc-invalid - Fired when validation fails, with error details
-
arc-submit - Fired on valid form submission with serialized form data
-
arc-reset - Fired when the form is reset via the .reset() method
See Also
- Input Versatile form control supporting single-line text, email, password, and multiline textarea modes with built-in label, placeholder, and validation states. Pairs with Form for complete data-entry workflows.
- Select Dropdown select with searchable options, keyboard navigation, and full ARIA listbox semantics for accessible form inputs.
- Checkbox Multi-select form control supporting checked, indeterminate, and disabled states. Ideal for preferences, bulk-selection patterns, and consent forms where users need to toggle one or more independent options.
- Toggle On/off switch with smooth animation, glow effect, and ARIA switch role.
- Textarea Multi-line text input with integrated label, placeholder, resize control, and live character count that turns red at the limit.
- Button Primary call-to-action element with three visual variants that map to action hierarchy. Supports prefix and suffix slots for icons. Renders as an anchor when given an href, making it ideal for navigation-driven actions across landing pages, toolbars, and forms.