Skip to Content
Forms

Forms

Numa uses @tanstack/react-form with GForm and field components (GInput, GPassword, etc.) from @numa/ui.

GForm

GForm is a declarative form builder. Pass an inputs array and actions array; GForm renders the form, wires TanStack Form, and handles validation.

How it works

  1. GForm calls useAppForm internally with defaultValues derived from your inputs
  2. For each input config, it renders a form.AppField and maps type to the correct field component
  3. Validation runs via TanStack Form validators — required: true adds a simple required check
  4. On submit, onSubmit(values) receives Record<string, string> with all field values

Basic example

<GForm name="login" direction="vertical" gap={4} inputs={[ { name: "email", type: "email", label: "Email", placeholder: "Email", bg: "primary", required: true, }, { name: "password", type: "password", label: "Password", placeholder: "••••••••", bg: "primary", required: true, }, ]} onSubmit={(values) => mutate({ body: values })} actions={[{ label: "Login", variant: "default", submit: true, loading: isPending }]} />

Props reference

PropTypeDefaultDescription
namestring"g-form"Form name and id
inputsGFormInputConfig[][]Input configs (ignored when children is used)
childrenReactNodeCustom form content (takes priority)
direction"vertical" | "horizontal""vertical"Flex direction for inputs
gapnumber6Spacing between inputs
cols1 | 2 | 3 | 4Grid columns (actions span full width)
onSubmit(values) => voidCalled with all field values on submit
actionsGFormActionConfig[][]Action buttons
defaultValuesRecord<string, string>Required when using children
classNamestringExtra classes on the form

Input config (GFormInputConfig)

FieldTypeRequiredDescription
namestringYesField name (used as form key)
typeGFormInputTypeYes"text" | "email" | "password" | "phone" | "textarea" | "select"
labelstringLabel above the input
placeholderstringPlaceholder text
optionsGSelectOption[]For type: "select" only
requiredbooleanAdds “Required” validator
validators{ onChange?, onBlur?, onSubmit? }Custom TanStack Form validators
initialValuestringDefault value

Action config (GFormActionConfig)

FieldTypeDescription
submitbooleanRenders as type="submit"
labelReactNodeButton text

Plus all TButtonProps: variant, size, loading, disabled, className, onClick, etc.

Layout modes

  • Flex: direction="vertical" (stacked) or "horizontal" (row, wraps)
  • Grid: When cols is set (1–4), inputs use grid layout, actions span all columns

Multi-column example

<GForm name="contact" cols={2} gap={4} inputs={[ { name: "firstName", type: "text", label: "First name", required: true }, { name: "lastName", type: "text", label: "Last name", required: true }, { name: "email", type: "email", label: "Email", required: true }, { name: "phone", type: "phone", label: "Phone" }, ]} onSubmit={(values) => sendContact(values)} actions={[{ label: "Send", submit: true }]} />

Custom validators

{ name: "password", type: "password", label: "Password", required: true, validators: { onChange: ({ value }) => value.length > 0 && value.length < 8 ? "At least 8 characters" : undefined, }, }

Manual form usage

For forms that need custom layout beyond GForm’s config, use the TanStack Form hooks directly:

const form = useAppForm({ defaultValues: { email: "", password: "" }, onSubmit: ({ value }) => { /* submit */ }, }); <form onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}> <form.AppField name="email" validators={{ onChange: ({ value }) => (!value ? "Required" : undefined) }} > {(field) => <field.GEmail label="Email" placeholder="Email" />} </form.AppField> <button type="submit">Submit</button> </form>

Field components

ComponentUse Case
field.GInputText input
field.GEmailEmail with validation
field.GPasswordPassword with visibility toggle
field.GPhoneNumberPhone number
field.GTextareaMulti-line text
field.GSelectSelect (requires options)
Last updated on