Form Wire
Guides

Nested Objects

Group fields into nested objects with fieldsets

Zod nested objects produce dotted field names and semantic <fieldset> groups.

Basic Nesting

const schema = z.object({
  profile: z.object({
    firstName: z.string().min(1),
    lastName: z.string().min(1),
  }),
  contact: z.object({
    email: z.email(),
  }),
});

This produces fields named profile.firstName, profile.lastName, and contact.email.

Configure them with dotted keys:

createFormWire(schema, {
  fields: {
    "profile.firstName": { label: "First name", placeholder: "Ada" },
    "profile.lastName": { label: "Last name", placeholder: "Lovelace" },
    "contact.email": { label: "Email", placeholder: "ada@form.dev" },
  },
});

Rendering

When auto-rendered, nested objects produce <fieldset> groups:

<fieldset>
  <legend>Profile</legend>
  <input name="profile.firstName" />
  <input name="profile.lastName" />
</fieldset>
<fieldset>
  <legend>Contact</legend>
  <input name="contact.email" />
</fieldset>

Default Values

Nest default values to match the schema shape:

<Form
  action={submit}
  defaultValues={{
    profile: { firstName: "Ada", lastName: "Lovelace" },
    contact: { email: "ada@form.dev" },
  }}
/>

Deep Nesting

You can nest objects multiple levels deep. Each level produces its own fieldset group and dotted field path.

Discriminated Unions

Zod discriminated unions are also supported:

z.discriminatedUnion("type", [
  z.object({ type: z.literal("personal"), name: z.string() }),
  z.object({ type: z.literal("business"), company: z.string() }),
]);

Form Wire shows only the fields that belong to the active discriminator value.

On this page