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.
**`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
- 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="jane@example.com" 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="jane@example.com" 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="jane@example.com" 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="jane@example.com" 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="jane@example.com" 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="jane@example.com" 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="jane@example.com" 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.
-
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.
-
disabledbooleanfalse - Disables the entire form, propagating the disabled state to every child field. Useful for read-only previews or while awaiting permissions.
-
error-summarybooleantrue - 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.
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.