Form

Lays out a form, with an optional title, description and footer, and wires up submit, loading and disabled states. Field state and validation stay with your app or form library.
Stable
Web
iOS
Android
@advui/core
Source

Create account

Enter your details.
JSX
<Form
  title="Create account"
  description="Enter your details."
  loadingText="Saving…"
  onSubmit={save}
  footer={
    <>
      <Button variant="outline">Cancel</Button>
      <Form.Submit>Save</Form.Submit>
    </>
  }
>
  <Field label="Name">
    <Input placeholder="Ada Lovelace" />
  </Field>
  <Field label="Email">
    <Input inputMode="email" placeholder="ada@example.org" />
  </Field>
</Form>
direction
gap

Installation

pnpm
pnpm add @advui/core
npm
npm install @advui/core

Usage

import { Button, Form, Field, Input } from '@advui/core'

const [saving, setSaving] = useState(false)

const save = async () => {
  setSaving(true)
  try {
    await saveUser()
  } finally {
    setSaving(false)
  }
}

<Form
  title="Create account"
  description="Enter your details."
  onSubmit={save}
  loading={saving}
  loadingText="Saving…"
  footer={
    <>
      <Button variant="outline">Cancel</Button>
      <Form.Submit>Save</Form.Submit>
    </>
  }
>
  <Field label="Name">
    <Input />
  </Field>
  <Field label="Email">
    <Input inputMode="email" />
  </Field>
</Form>

Examples

Basic

Fields stacked with a consistent gap.

Title and description

Create account

Enter your details. You can change them later.

Loading

The app sets `loading` around the request. Press Save several times: presses while saving are ignored.
Not saved yet.

Horizontal

A compact search form in one row. Give the input `flex` to share the row.

Disabled

Reporting period

The 5W round is closed. Editing opens again next round.

With your own validation

The app (or React Hook Form, Formik…) owns values and errors; Field shows them.

Subscribe

Get the monthly update by email.

Custom styling

Style props and themes work as on any view.

Sign in

API reference

Form

A `<form>` on web and a View on native, built on Tamagui’s Form. It takes every view prop (`padding`, `backgroundColor`, `borderRadius`, `width`, `theme`, media props, `testID`, `aria-*`…). It never clones or changes its children.
PropTypeDefaultDescription
childrenReactNode—The fields: Field, inputs, checkboxes, your own components…
gapSpaceTokens | number'4'Space between the fields.
titleReactNode—Text becomes a level-2 heading that names the form. Pass an element (e.g. `<Heading level={1}>`) for another level.
descriptionReactNode—Muted text under the title; describes the form.
footerReactNode—Actions under the fields, right-aligned. Put anything here: Cancel, `Form.Submit`, links. No submit button is assumed.
onSubmit() => void | Promise<unknown>—Called by `Form.Submit` and, on web, by Enter in a field. Not called while `disabled` or `loading`. A returned Promise is not awaited: set `loading` yourself.
loadingbooleanfalseThe app is submitting. `Form.Submit` shows a spinner and is busy, and `onSubmit` is blocked. Fields stay editable.
loadingTextReactNode—Replaces the `Form.Submit` label while loading, e.g. "Saving…", and is announced once on web.
disabledbooleanfalseDisables every form control inside, in a Field or not (Input, Textarea, Select, Checkbox, Switch, Radio Group, pickers…), and `Form.Submit`, and blocks `onSubmit`. Other buttons, such as Cancel, stay enabled.
direction'vertical' | 'horizontal''vertical'`horizontal` puts the fields in a wrapping, bottom-aligned row: for search bars and filters.
fullWidthbooleanfalseThe form fills its container, and footer actions stack at full width. Fields fill the width in either case.

Form.Submit

A Loading Button that submits the form: a submit button on web (so Enter in a field submits too), and a press that calls `onSubmit` on native. Takes every Loading Button prop.
PropTypeDefaultDescription
loadingbooleanthe form's `loading`Spinner, `aria-busy` and blocked presses.
loadingTextReactNodethe form's `loadingText`Label while loading.
disabledbooleanfalseAlso disabled while the form is.

useParentForm

Reads the surrounding Form, for your own fields and actions; returns a `FormContextValue`. Outside a Form it returns the defaults, and `submit` does nothing. It was called `useFormStatus` (still exported, deprecated), which is also the name of a React DOM hook for Server Actions.
PropTypeDefaultDescription
disabledboolean—The form's `disabled`.
loadingboolean—The form's `loading`.
loadingTextReactNode—The form's `loadingText`.
submit() => void—Calls `onSubmit` unless disabled or loading, e.g. from a custom button or `onSubmitEditing` on native.

Accessibility

  • On web it is a <form>. With a title it is named by it (aria-labelledby) and described by description, so screen-reader users can jump to it as a form landmark. Without a title, pass aria-label if it should be a landmark.
  • A text title is a heading (level 2, header role on iOS and Android); pass your own Heading for another level.
  • Enter in a field submits on web, as in any HTML form with a submit button. On native, call submit from useParentForm() in onSubmitEditing if Return should submit.
  • While loading, Form.Submit is busy (aria-busy) and disabled, and loadingText is read once from a polite status region on web. The form itself is not marked busy, so its fields stay readable.
  • While disabled, every form control inside is disabled (aria-disabled and not editable), and Field labels are dimmed. Form never clones its children: the core controls read the form through context, and your own controls can call useFieldControl(props) or useParentForm(). Buttons other than Form.Submit stay enabled, so Cancel still works.
  • Browser validation is off (noValidate) so errors come from your app, the same on every platform: show them with a Field’s error.
Keyboard interactions
KeyAction
EnterIn a field: submits the form (web).
Enter / SpaceOn Form.Submit: submits; ignored while loading or disabled.
TabMoves through the fields and actions in order.

Responsive behavior

Vertical forms fill their container and stack fields. Horizontal forms wrap fields to the next line when they run out of room; give fields flex and minWidth. Use flat values (padding="4 sm:6") for per-breakpoint styles.

Platform notes

Web
Renders <form noValidate>; Form.Submit is <button type="submit">.
iOS
No form element: Form.Submit calls onSubmit on press. The loading status is read from the busy button.
Android
Same as iOS.
Since v0.6.0Last updated Nov 2, 2026Edit this page on GitHub