FFormura

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. 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. 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. 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. 4

    Result → state

    Your action returns an ActionResult. If it contains fieldErrors, they are mapped onto the form. The result is available via useFormState() 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/adapters

Your 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:

app/globals.css
@import "@formura/adapters/tailwind.css";

Quick start

Three files. That is the entire integration. Define a schema, tag an action, call createForm.

schema.ts
import { z } from "zod";

export const signupSchema = z.object({
  username: z.string().min(2, "At least 2 characters"),
  email: z.email("Invalid email"),
});
actions.ts
"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" } };
});
signup-form.tsx
"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.