Form
Forms coordinate validation, default values, and submission across RuiField children without external form libraries.
Try it
Usage
Wrap fields in RuiForm and handle validated values with onSubmit. Choose mode to control when validation runs.
import { RuiForm } from '@ecopages/radiant-ui/form';
import { RuiField } from '@ecopages/radiant-ui/field';
import { RuiLabel } from '@ecopages/radiant-ui/label';
import { RuiInput } from '@ecopages/radiant-ui/input';
import { RuiButton } from '@ecopages/radiant-ui/button';
<RuiForm mode="onSubmit" onSubmit={handleSubmit}>
<RuiField name="name" rules={{ required: 'Name is required' }}>
<RuiLabel>Full name</RuiLabel>
<RuiInput />
</RuiField>
<RuiButton type="submit">Create account</RuiButton>
</RuiForm>Load @ecopages/radiant-ui/styles.css once in your app to style the form and every composed control. When bundling styles per component, import @ecopages/radiant-ui/form/styles.css plus the stylesheet for every component used inside the form, such as field/styles.css, textarea/styles.css, or number-field/styles.css.
Validation timing
onSubmit validates once on submit and is best for short forms. Use onBlur or onChange when immediate feedback helps data entry.
Compose with Radiant controls
Every RuiField child should be a Radiant control (RuiInput, RuiSelect, RuiDateField, …). The form store discovers values through the Field control protocol, unmarked native inputs are not registered.
Default values
Pass defaultValues to pre-populate edit forms. Fields read their initial state from the form store.
Theming
Form surfaces map to semantic surface roles, never Tailwind palette steps:
| Part | CSS roles |
|---|---|
Root (.rui-form) | space-stack gap |
The form itself carries no color roles; fields, labels, and controls inside keep their own surface / control tokens. Override at the theme layer, not in component CSS.
API
RuiForm is a custom element (<rui-form>). The RuiForm view is a passthrough that maps defaultValues / resolver to prop: bindings.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
mode | onSubmit · onBlur · onChange · onTouched · all | onSubmit | When validation runs. |
revalidate-mode | onSubmit · onBlur · onChange · onTouched · all | onChange | When fields re-validate after a failed submit. |
data-default-values | string | JSON-serialized default values for SSR / JSX attribute channel. | |
action | string | Native form destination when onSubmit is not provided. | |
method | string | Native form HTTP method when onSubmit is not provided. |
Props
| Prop | Type | Description |
|---|---|---|
defaultValues | Partial<FieldValues> | Initial values; fields read their state from the form store. |
resolver | Resolver<T> | Custom validation resolver (e.g. from createRulesResolver). |
onSubmit | (values: T) => void | Promise<void> | Receives validated values. When omitted, an action or method submits the native form after validation passes. |
Context
formContext exposes the current errors map to custom consumers. RuiField reads the matching field state and applies it to its control as aria-invalid; use fieldContext for a control's local error and invalid values.
Events
| Event | Detail | Description |
|---|---|---|
rui-submit | { values: T } | Emitted when validation passes. |
rui-invalid | { errors: Record<string, { message?: string }> } | Emitted when validation fails on submit. |
CSS classes
Public BEM classes (documented via @cssclass):
| Class | Description |
|---|---|
.rui-form | Root form surface (<form noValidate>). |
Theme roles
| Part | CSS variables consumed |
|---|---|
| Root | --space-stack |
Accessibility
- Submit buttons should use
type="submit"inside the form for keyboard submission. - Announce form-level errors at the top when multiple fields fail validation.
- Focus the first invalid field after a failed submit attempt.