Getting Started
Formura is a schema-first form library for React and Next.js. Give createForm a Zod object schema, wire a server or client action, and get auto-rendered fields, built-in validation, field-level errors, and typed submission state — no register calls, no field arrays, no adapter boilerplate.
How Formura thinks
Formura has one job: turn a Zod schema into a fully wired form. Here is exactly what happens — no magic, just a four-step pipeline:
- 1
Schema → field keys
Formura reads every key from your
z.object()schema and automatically determines the right widget — text input, select, checkbox, date picker, OTP, and more — based on the Zod type. - 2
Adapter → components
The adapter receives the field metadata and renders the appropriate UI component — Input, Select, Checkbox, DatePicker, etc. — wired to the form store automatically.
- 3
Submit → validate → action
On submit, Formura validates values against your schema client-side first. If validation fails, errors are mapped to fields immediately and the action is never called. If it passes, your action receives the validated data.
- 4
Result → state
Your action returns an
ActionResult. If it containsfieldErrors, they are mapped onto the form. The result is available viauseFormState()for success/error UI.
What you do not write with Formura:
- No
register()calls or manual field wiring - No separate validation schema vs form schema
- No manual FormData parsing in server actions
- No submission state boilerplate (isPending, error, result)
- No field-level error propagation code
- No adapter config beyond a single import
Install
npm install @formura/core @formura/adaptersYour app needs Tailwind CSS so adapter utility classes apply.
Prerequisites
- React 18.2+ or 19
- Next.js 14+ (required for Server Actions — client actions work anywhere)
- Zod 4+
- Tailwind CSS 4+
Tailwind setup
Include adapter sources in your Tailwind content scan so utility classes from bundled components are picked up:
@import "@formura/adapters/tailwind.css";Quick start
Three files. That is the entire integration. Define a schema, tag an action, call createForm.
import { z } from "zod";
export const signupSchema = z.object({
username: z.string().min(2, "At least 2 characters"),
email: z.email("Invalid email"),
});"use server";
import { asServerAction } from "@formura/core/server";
export const signupAction = asServerAction(async (_prevState, formData) => {
const username = formData.get("username");
if (username === "admin") {
return {
status: "error",
fieldErrors: { username: "Reserved." },
};
}
return { status: "success", data: { userId: "user_123" } };
});"use client";
import { createForm } from "@formura/core";
import shadcnAdapter from "@formura/adapters/shadcn";
import { signupSchema } from "./schema";
import { signupAction } from "./actions";
const { Form, useFormState } = createForm({
schema: signupSchema,
adapter: shadcnAdapter,
action: signupAction,
defaultValues: { username: "", email: "" },
});
export const SignupForm = () => {
const { isSubmitting } = useFormState();
return (
<Form className="space-y-4">
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Signing up..." : "Sign up"}
</button>
</Form>
);
}When an adapter is provided, every key in your schema becomes a field automatically — no <Field /> declarations needed. Children inside <Form> that are not <Field> elements (like submit buttons) are rendered after the auto-generated fields.
Try it live
See a working signup form with Server Actions and field-level errors.
Open the signup example →Next
Actions
Server Actions, client actions, and the ActionResult contract.