forms
Build forms with validation, and show server errors next to the right field.
What it is
@kitr/forms is React Hook Form plus the kitr pieces. useSchemaForm builds a form from a schema, so the type and the rules are one thing. <Form> submits to an action or API route, shows a pending state and puts server errors on the right fields.
Runs in the browser. The server half is actions or API routes.
Install
kitr add formsFirst time? Run npm i -g @kitr/kitr once. Inside a project, the global kitr runs your project's own version.
Adds @kitr/forms with react-hook-form, its resolvers and zod. No provider, no config file. Every change.
Quick start
Start with a schema, then build the form from it. Each field is a <FormField> with an input inside:
// app/features/invoices/new-invoice-form.tsx
import { FieldInput, Form, FormField, SubmitButton, useRootError, useSchemaForm } from '@kitr/forms'
import { z } from 'zod'
const newInvoiceSchema = z.object({
customerEmail: z.email('Enter the customer’s email'),
amount: z.coerce.number().positive('Amount must be more than zero'),
})
type NewInvoice = z.output<typeof newInvoiceSchema>
export function NewInvoiceForm({ onCreate }: { onCreate: (invoice: NewInvoice) => Promise<unknown> }) {
const form = useSchemaForm(newInvoiceSchema, { defaultValues: { customerEmail: '', amount: '' } })
return (
<Form form={form} onSubmit={onCreate} resetOnSuccess aria-label="New invoice">
<FormField name="customerEmail" label="Customer email">
<FieldInput type="email" />
</FormField>
<FormField name="amount" label="Amount (USD)">
<FieldInput inputMode="decimal" />
</FormField>
<RootError />
<SubmitButton pendingText="Creating…">Create invoice</SubmitButton>
</Form>
)
}
function RootError() {
const error = useRootError()
return error ? <p role="alert">{error}</p> : null
}
Render <NewInvoiceForm onCreate={async (invoice) => console.log('created', invoice)} /> on a page. Tab out of the empty email field and "Enter the customer’s email" appears under it. Fill in [email protected] and 120, press the button, and the console prints:
created { customerEmail: '[email protected]', amount: 120 }
amount arrives as the number 120, because onSubmit receives the schema's output. While onCreate runs the button reads "Creating…". Next in the guide: Build your first form.
Built on
| What kitr adds | Why |
|---|---|
useSchemaForm |
React Hook Form with a Standard Schema resolver, so zod, valibot or arktype schemas type the form. |
<Form> |
Submits a mutation, shows a pending state, and maps KitrError.fieldErrors onto fields. |
FormField, useFieldProps, SubmitButton |
Headless field wiring with aria-* attributes and data-kitr attributes for styling, and a button that follows the pending state. |
kitr make:form |
A generator for a form file with its schema. |
kitr adds useSchemaForm, <Form>, the field components with accessibility attributes and the server error mapping.
Problems it solves
- Validation rules and types written twice. Build your first form.
- Server errors in a generic alert. Show server errors on fields.
- Double submits. Show a pending state.
- Editing an existing record. Edit an existing record.
- Inputs the library does not know about. Wrap a controlled component.
- Inaccessible forms. Make forms accessible.