Form

Form

FormKit and FieldKit — Zod schemas with React Hook Form.

The form workspace package provides schema-driven controls for product forms and inline field surfaces. FormKit owns the submit lifecycle; FieldKit owns field state without adding a submit button. Both use Zod, React Hook Form, and the same field registry.

When to use it

PatternUse
FormKitCreate/edit screens with validation, server errors, and submit/reset actions
FieldKitToolbar filters, drawer filters, or any UI where you own values / onValuesChange
Low-level Field* exportsCustom layouts outside FormKit/FieldKit (advanced)

Reach for FormKit when the page has a single schema and an onSubmit. Reach for FieldKit when the parent already stores filter state (often next to a DataTable).

Installation

Registry CLI

Requires a one-time components.json registry entry for @feature-kit/form.

pnpm dlx shadcn@latest add https://feature-kits.vercel.app/r/form.json

Namespaced: npx shadcn@latest add @feature-kit/form

Manual installation

Monorepo consumers can depend on workspace:* packages instead of the registry CLI.

pnpm add form layout ui

Dependencies

// apps/your-app/package.json
"dependencies": {
    "form": "workspace:*"
    "layout": "workspace:*"
    "ui": "workspace:*"
}

Transpile (Next.js)

// next.config.ts
transpilePackages: ["form", "layout", "ui"]

Import

import { FormKit } from "@/lib/feature-kit/form"
import { FieldKit } from "@/lib/feature-kit/form/field-kit"
import { FormSection } from "@/lib/feature-kit/layout"

Apps need next-intl messages for field labels if you use translated error keys.

Choose an API

NeedUse
Create or edit data with submit, reset, pending, and server-error handlingFormKit
Controlled filters or inline fields next to a tableFieldKit
A custom layout around one controlField* exports from form or form/field
Searchable option controlsSearchableSelect or SearchableMultiSelect

FormKit and FieldKit are client components. Keep the schema and default values in the client component that renders them.

Anatomy

FormKit is a compound component:

  • FormKit — creates the form context from schema + defaultValues
  • FormKit.Field — dispatches to the correct control from type
  • FormKit.Actions — footer row for buttons
  • FormKit.Submit / FormKit.Reset — wired to form state (isPending, dirty, etc.)

FieldKit mirrors the same Field API with mode="controlled" (default) or mode="uncontrolled".

Usage

Basic profile form

Profile

Typical create/edit layout with actions in the footer.

Field types

FormKit.Field accepts a discriminated type. Supported values in this repo:

typeControl
textText input (inputType: text, email, tel, password)
textareaMultiline text
select / multi-selectSearchable lists
checkbox / switchBoolean
checkbox-choice-card / switch-choice-card / radio-choice-cardCard-style choices
radio-groupRadio list
date / date-range / month / quarter / timeDate & time pickers

See Field types example for a live matrix.

Validation

Pass a Zod schema to FormKit. The resolver validates before onSubmit runs and focuses the first invalid field. Use Zod for interaction feedback, but keep authoritative business validation on the server.

const schema = z.object({
  email: z.string().email("Enter a valid email"),
  terms: z.boolean().refine(Boolean, "Accept the terms"),
})

The validation example demonstrates string, numeric-string, regex, range, and boolean rules.

Filter row (FieldKit)

Use FieldKit when there is no form submit—only value changes:

import { FieldKit } from "form/field-kit"
import { z } from "zod"

const filterSchema = z.object({
  q: z.string(),
  status: z.enum(["all", "active", "archived"]),
})

<FieldKit
  schema={filterSchema}
  values={filters}
  onValuesChange={setFilters}>
  <FieldKit.Field type="text" name="q" label="Search" />
  <FieldKit.Field type="select" name="status" label="Status" options={statusOptions} />
</FieldKit>
Live preview with sample data in the docs. FieldKit filters preview

Recipes

Server validation errors

Map API failures with mapServerError or call applyServerErrors from the form package after a failed mutation. FormKit calls the mapper when onSubmit throws, applies field/form errors, and focuses the first invalid field. The mapper returns the public MapServerErrorResult shape; the package also exports mapUnknownServerError, resolveFieldErrorMessage, and focusFirstError.

Do not call an undocumented form method to set errors. Throw the mutation error from onSubmit and provide mapServerError, or use applyServerErrors(form.setError, mapped) in a custom React Hook Form integration.

Live preview with sample data in the docs. Validation & server errors preview

Multi-section pages

Set scrollBody={false} on FormKit when sections live in a parent scroll container (long settings pages). Wrap each group in FormSection from layout.

Loading and pending states

Use isPending for an external mutation state. FormKit.Submit also disables itself while React Hook Form is submitting and displays pendingLabel. FormKit.Reset is disabled while isPending. For initial data loading, render the appropriate Field*Skeleton export before mounting the editable form; skeletons are separate controls, not a FormKit prop.

FieldKit

FieldKit is useful when the host owns the values:

<FieldKit
  schema={filterSchema}
  mode="controlled"
  values={filters}
  onValuesChange={setFilters}>
  <FieldKit.Field type="text" name="q" label="Search" />
</FieldKit>

Controlled mode uses values and onValuesChange. Uncontrolled mode uses defaultValues; mode defaults to "uncontrolled" in the implementation. FieldKit has no submit, reset, or success lifecycle. See the FieldKit example for a controlled filter surface.

API reference

FormKit

FormKit props
PropTypeDescription
schemaZodTypeAnyZod schema used for resolver validation and output typing.
defaultValuesDefaultValues<z.input<TSchema>>Initial values; must satisfy the schema input shape.
onSubmit(values: z.output<TSchema>) => void | Promise<void>Called with parsed output when client validation passes.
isPendingbooleanDisables submit and shows pending UI on Submit.
resetOnSuccessboolean | "keepDirtyValues" | DefaultValuesReset strategy after a successful submit.
mapServerError(error: unknown) => MapServerErrorResultMaps thrown errors to field or form messages.
translateError(message: string) => stringLocalizes Zod / server message keys.
scrollBodybooleanWhen false, omit internal scroll body for nested page layouts.

FieldKit

FieldKit props
PropTypeDescription
schemaZodTypeAnyShape of filter or inline field values.
mode"controlled" | "uncontrolled"Controlled uses `values` + `onValuesChange`; uncontrolled uses `defaultValues`.
valuesz.input<TSchema>Required in controlled mode.
onValuesChange(values) => voidCalled when any field changes in controlled mode.

Advanced usage

Reset after success

Set resetOnSuccess to true, "keepDirtyValues", or an object of default values. true restores the original defaultValues; "keepDirtyValues" preserves edits that were made while the submission completed.

Multi-section forms

Use FormSection from layout to group fields. Set scrollBody={false} when the surrounding page owns scrolling. FormKit.Actions places submit controls in the form action area.

Low-level field controls

The package exports FieldText, FieldSelect, FieldDate, FieldDateRange, FieldMonth, FieldQuarter, FieldTime, boolean/card/radio variants, skeletons, fieldRegistry, and FormFieldView. Use these when composing a custom field layout; use FormKit.Field or FieldKit.Field for normal schema-driven forms.

API and exports

The package export map supports:

ImportUse
formFormKit, FieldKit, field controls, comboboxes, utilities, and public types
form/field-kitFieldKit and its public types
form/form-kitFormKit and its public types
form/fieldfield controls and field types

The root package also re-exports Controller, useFormContext, useFormState, and useWatch from React Hook Form. Internal source paths under feature/form/src are not package imports.

FormKit.Submit / FormKit.Reset

Action props
PropTypeDescription
labelReactNodeButton label.
pendingLabelReactNodeShown on Submit while pending.
disabledbooleanForce disabled state.
classNamestringOptional class on the button.

Gotchas

Client components

FormKit and FieldKit are client components. Import them from client files or wrap in a "use client" module.

Numbers

Avoid type="number" inputs. Use text with inputMode="numeric" and Zod coercion if you need numeric fields—plain number inputs drop leading zeros.

i18n

Table and form kits expect next-intl for pagination and some labels. The playground app loads messages/en.json in apps/web/src/app/providers.tsx.

Examples

Live preview with sample data in the docs. Basic formLive preview with sample data in the docs. All field typesLive preview with sample data in the docs. Validation

Package exports: form, form/field-kit, form/field (low-level field components).

On this page