FFormura

Formura

One schema to rule them all, one adapter to render them.

How it works

Schema alchemy in three steps

No field arrays. No register() calls. Your Zod schema is the single source of truth.

Step 1

Define your schema

const schema = z.object({
  username: z.string().min(2),
  role: z.enum(["dev", "design"]),
  terms: z.boolean(),
});

Step 2

Create the form

const { Form, Field } = createForm({
  schema,
  adapter: shadcnAdapter,
  action: signupAction,
});

Step 3

Render fields

<Form className="space-y-4">  
  {/* Fields auto-render from schema */}
  <button type="submit">Submit</button>          
</Form>

Live demo

Watch your schema become a form

This form is rendered entirely from the Zod schema on the left. Try submitting — or type 'taken' as username to see field errors.

schema.ts
const demoSchema = z.object({
  username: z.string().min(2),
  email: z.email(),
  role: z.enum(["developer", "designer", "manager"]),
  terms: z.boolean(),
});
rendered form

Features

Everything you need, nothing you don't

Form libraries shouldn't require a PhD in useEffect. Formura gets out of your way.

Zero boilerplate

Define a Zod schema once. Formura infers widgets, labels, and validation automatically.

Server Actions

Tag your server action with asServerAction and Formura handles prevState, FormData, and field errors.

Client actions

Use asClientAction for client-side submissions with the same unified API and error handling.

Widget inference

Strings, numbers, enums, booleans — each maps to the right input. Override with .describe('widget:otp').

shadcn adapter

Beautiful defaults out of the box. Swap primitives or extend createShadcnAdapter for your design system.

App Router ready

Built for Next.js with React 19. Server and client components work together seamlessly.

Widgets

Inferred from your schema

Formura reads Zod types and metadata to pick the right widget. Override when you need to.

text

z.string()

password

.describe("widget:password")

otp

.describe("widget:otp")

number

z.number()

select

z.enum([...])

multiSelect

z.array(z.enum([...]))

textarea

.describe("widget:textarea")

checkbox

z.boolean()

Stop handwriting forms.

Every handwritten form is a cry for help.