Skip to contentAsheeUIAsheeUI

Form

The container that groups fields, submits them, and owns the pending state.

Form is the container the fields sit in, not a field itself. It wraps the native form element so that grouping, submission and the busy state have one home, without the framework taking responsibility for your form state: there is no field registry, no validation schema and no submission state of its own, isPending is a prop you set, not a status it discovers. Everything the platform's form does, action, method, encType, target, noValidate, the submit event, passes through untouched.

Usage

import { Form, Input } from "@asheeui/web";

<Form legend="Invoice details" submitLabel="Save invoice" onSubmit={saveInvoice}>
  <Input label="Reference" name="reference" />
  <Input label="Email" name="email" type="email" />
</Form>

It owns three things and nothing else: the legend that groups the fields, the submit control that commits them, and the pending state that control shows while a submission is in flight. Give it a legend and the fields are wrapped in a fieldset with that legend; give it a submitLabel and it renders an AsheeUI Button in the row at the foot of the form. Omit either and that part is yours.

The form does not own the values. onSubmit is the native event, so a controlled field, a form library or plain FormData all bind to the form exactly as they would to a <form>. For the whole shape, values, validation, focus and submission, see Building a form.

The pending state

isPending marks a submission in flight. The submit control is disabled and busy while it is true, which stops the second press that a slow request invites, and it keeps its label, so the row does not move under the pointer.

The fields stay editable: the pending state is about the control that commits, not about freezing the form, so a reader can still correct a value while the request is in flight.

Without a legend or a control

Both escape hatches are ordinary omissions. Without legend there is no fieldset, so name the form yourself with aria-label or aria-labelledby; without submitLabel there is no button, so place your own, a type="submit" control inside the form still fires the native submit event.

The element itself can be replaced the way the framework's Link replaces an anchor: component names a form primitive and componentProps carries the props it needs, so a framework's own form component sits where the native one did.

Props

The defaults describe a control rather than a layout. isPending is off; without legend and submitLabel the component renders no grouping and no control. The submit control's variant and colour come from the application's defaultVariant and defaultColor, faded and primary out of the box, while its size is md and its radius is registered as md. All four can be restated once under components.form as submitVariant, submitColor, submitSize and submitRadius, or overridden per form with the props of the same names.

Accessibility

  • The element is a real <form>, so native submission, validation and autofill work, and a submission triggered by Enter in a single-line field is the browser's, not a simulation of it.
  • With legend, the fields are a fieldset named by a legend, and the group is announced before its controls. Without it the component adds no name of its own: give the form aria-label or point aria-labelledby at the heading that describes it.
  • The submit control is a <button type="submit">, so it is reached and pressed by keyboard like any other button and it submits without a click handler. While isPending it is disabled and busy through aria-disabled and aria-busy, and its label does not change, the announcement says what is happening, not what replaced the label.
  • The form never moves focus, on mount or after a rejected submission. Where focus goes after a validation failure is your decision, which is why the component does not make one.