FFormura

Widget Reference

Formura infers the right widget from your Zod schema. Use .describe("widget:<name>") when inference is ambiguous.

Inference table

Formura reads your Zod field type — and optionally a .describe() hint — to choose the correct widget automatically. Optional, nullable, and default wrappers are stripped before inference.

Zod typeWidget
z.string()text
z.string().describe("widget:password")password
z.string().describe("widget:otp") or fixed length 4–8otp
z.string().describe("widget:textarea")textarea
z.number()number
z.enum([...])select
z.array(z.enum([...]))multiSelect
z.boolean()checkbox
z.coerce.date() / z.date()date
z.string().describe("widget:time")time
z.coerce.date().describe("widget:datetime")datetime

Widget hints

Add .describe("widget:<name>") to any Zod field to override or guide inference. Formura parses the widget: prefix from the description string using a regex — any other description content is ignored. Valid widget names are:

textpasswordnumberotpselectmultiSelecttextareacheckboxdatetimedatetime
// Explicit widget hints
password: z.string().min(8).describe("widget:password")
bio:      z.string().max(500).describe("widget:textarea")
code:     z.string().length(6).describe("widget:otp")
start:    z.string().describe("widget:time")
meeting:  z.coerce.date().describe("widget:datetime")

The description string can contain other text — only the widget: portion is parsed. For example, .describe("User bio widget:textarea") works fine.

Optional, nullable, and default

Formura unwraps Zod modifier wrappers before inferring the widget. You can mark fields optional or give them defaults without affecting the rendered widget:

// All of these still render a "text" widget
username: z.string()
username: z.string().optional()
username: z.string().nullable()
username: z.string().default("")

// Wrappers are stripped up to 16 layers deep
username: z.string().optional().default("").nullable()

Per-widget examples

textz.string()
username: z.string().min(2, "At least 2 characters")

Default for all strings without a widget hint. Renders a plain text input.

passwordz.string().describe("widget:password")
password: z.string().min(8).describe("widget:password")

Renders an input with type="password". Inference won't guess this on its own — always add the hint.

otpz.string().describe("widget:otp") or fixed length 4–8
code: z.string().length(6).describe("widget:otp")
// Auto-inferred when minLength === maxLength and the value is 4–8
verificationPin: z.string().min(6).max(6)

OTP length is read from the schema's min/max constraints. Falls back to 6 slots when ambiguous.

textareaz.string().describe("widget:textarea")
bio: z.string().max(500).describe("widget:textarea")

Renders a resizable textarea. Zod max() acts as a visual guide — HTML maxLength is not set automatically.

numberz.number()
age: z.number().min(18).max(120)

Renders type="number". The field value is a JavaScript number, not a string — no manual coercion needed.

selectz.enum([...])
role: z.enum(["developer", "designer", "manager"])

Options are derived from the enum values. Labels match values by default — use the fields config to rename them.

multiSelectz.array(z.enum([...]))
skills: z.array(z.enum(["react", "node", "design"]))

Renders a checkbox list. Value is a string array matching the selected enum values.

checkboxz.boolean()
terms: z.boolean().refine((v) => v, "You must accept the terms")

Renders a single checkbox. Value is a native boolean.

datez.coerce.date() / z.date()
birthday: z.coerce.date()
// z.coerce.date() is preferred — it accepts ISO strings from FormData

Renders a calendar date picker. Value is a JavaScript Date object with the time component set to midnight.

timez.string().describe("widget:time")
startTime: z.string().describe("widget:time")

Value is a string in "HH:mm" 24-hour format (e.g. "14:30"). Inference will not guess time from z.string() alone — the hint is required.

datetimez.coerce.date().describe("widget:datetime")
scheduledAt: z.coerce.date().describe("widget:datetime")

Renders a combined calendar + time picker. Value is a JavaScript Date object with both date and time components.

Date and time value types

Date and time widgets each have a distinct value type. Make sure your schema matches:

WidgetValue typeExample value
dateDatenew Date("2026-07-08")
timestring"14:30"
datetimeDatenew Date("2026-07-08T14:30:00")

Use z.coerce.date() instead of z.date() when the value may arrive as an ISO string (e.g. from a Server Action via FormData).

Custom shadcn components

Extend the default adapter when you need to swap primitives from your local shadcn install. See Adapters.

import { createShadcnAdapter } from "@formura/adapters/shadcn";

const adapter = createShadcnAdapter();

Next

Adapters

Tailwind setup, default shadcn adapter, and custom adapters.