Form Wire
Core Concepts

Schema

How Zod schemas drive form field generation

Form Wire introspects your Zod schema to generate field metadata automatically. You don't define fields manually — the schema is the source of truth.

Supported Field Types

Zod TypeForm Field KindUI Control
z.string()string<input type="text">
z.email()string<input type="email">
z.coerce.number()number<input type="number">
z.coerce.date()date<input type="date">
z.boolean()boolean<input type="checkbox">
z.literal(true)boolean<input type="checkbox"> (must be checked)
z.enum([...])select<select>
z.literal("value")select<select> (single option)
z.file()file<input type="file">
z.string().array()stringArrayRepeated text inputs
z.object({...}).array()objectArrayFieldset groups
z.object({...})Nested group<fieldset>

String Subtypes

Form Wire detects common string formats and maps them automatically:

z.email()           // → type="email"
z.coerce.number()    // → type="number"
z.coerce.date()      // → type="date"
z.string()           // → type="text"

Force a textarea using the component option in field config:

createFormWire(schema, {
  fields: {
    bio: { label: "Bio", component: "textarea" },
  },
});

Optional vs Required

Zod determines whether a field is required:

z.string()           // required (must be non-empty)
z.string().optional() // not required
z.string().nullable() // not required, parses as null when empty

Select Fields

Any union of string literals generates a <select>:

z.enum(["pending", "active", "paused"])
// → <select> with three options

Enum with Labels

Option labels come from the field config options or are generated from the enum values. Prefer explicit options for real application labels:

createFormWire(schema, {
  fields: {
    status: {
      label: "Status",
      placeholder: "Choose a status",
      options: [
        { label: "Draft / internal", value: "pending" },
        { label: "Active (public)", value: "active" },
        { label: "Paused - billing", value: "paused" },
      ],
    },
  },
});

For dynamic data, pass options to the rendered field:

<InvoiceForm.Field
  name="companyId"
  options={companies.map((company) => ({
    label: company.name,
    value: company.id,
  }))}
/>

Schema Metadata

Use withMeta() to attach label and description directly to schema nodes:

import { withMeta } from "@form-wire/core";

const schema = z.object({
  name: withMeta(z.string().min(1), {
    label: "Full Name",
    description: "Your legal name",
    placeholder: "Ada Lovelace",
  }),
});

Metadata from withMeta is merged with field config from createFormWire — config takes precedence.

On this page