Appica UIUI 1.1.0

Forms & Validation

Build accessible forms with validation.


Forms are built from two pieces: Field, which groups a control with its label, description, and error message and wires up the accessibility relationships; and Form, which coordinates the fields and surfaces server-side errors. Both wrap Base UI primitives.

Anatomy of a field

A Field composes a label, a control, an optional description, and an error slot. The for/id and aria-describedby associations are handled for you.

Validation

Native constraints

Standard HTML attributes - required, type, min, maxLength, pattern - are validated automatically. Point FieldError at a specific failure with match, or render the browser's default message with a bare <FieldError />:

Custom validation

For rules HTML can't express, pass a validate function to the Field. Return a string (or array of strings) to fail, or null to pass. Control when it runs with validationMode ('onBlur' or 'onChange'):

validate can be async (return a promise) which is handy for availability checks against an API.

Shake on invalid

For an extra cue when validation fails, hang the animate-shake utility off the control's invalid state. Base UI sets data-invalid on the control when the field fails validation, so put the class on the Input to shake just the control:

Grouping with Fieldset

Use Fieldset and FieldsetLegend to group related fields under a shared label (an accessible <fieldset>/<legend>):

The Form wrapper

Form ties the fields together and handles submission. Its most useful job is displaying server-side errors: return them keyed by field name and pass them to errors, and each Field shows its message in place.

Submitting to a server action

Form renders a real <form>, so it takes an action. Every control that carries a name ends up in the FormData, including the ones that aren't native inputs: Select and Checkbox render their own hidden input for exactly this.

You don't repeat the name on the control. Field's name propagates to whatever it wraps, and it's the same key Form matches server errors against.

app/signup/actions.ts
app/signup/signup-form.tsx

Returning { errors } from the action feeds Form, which routes each message to the Field whose name matches, so server-side validation renders in the same place as client-side validation.

Accessibility

Labels, descriptions, and error messages are associated with their control automatically, and invalid fields are marked with aria-invalid. See Accessibility - and note that grouped controls (RadioGroup, CheckboxGroup) are labeled with aria-label/aria-labelledby, not FieldLabel.

© 2026 Appica UI. A free component library, crafted by the Appica team.