Overview
FUAutoForm infers a FUDataEdit schema instead of requiring you to write one. Pass a plain data object and every field gets a matching input based on its value type; or pass a Valibot or Zod schema and inputs, labels, and validation rules are derived from the schema's own structure. The inferred schema is handed to FUDataEdit unchanged, so everything you know about FUDataEdit (props, events, slots) applies as-is.
<template>
<FUAutoForm
:data="user"
@data-saved="handleSave"
/>
</template>
<script setup lang="ts">
const user = ref({
firstName: 'Ada',
age: 36,
newsletter: true,
tags: ['vue', 'nuxt'],
address: { city: 'London' },
contacts: [{ email: 'ada@example.com' }],
})
function handleSave(data: any) {
console.log('Saved:', data)
}
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Inference Rules
| Value shape | Inferred input |
|---|---|
string | nuxtUIInput |
| string containing a line break | nuxtUITextarea |
ISO date string (2024-01-15 or full ISO-8601 datetime) | nuxtUIInputDate with valueType: 'iso' (round-trips as string) |
Date instance | nuxtUIInputDate with valueType: 'date' |
@internationalized/date CalendarDate/CalendarDateTime/ZonedDateTime instance | nuxtUIInputDate with valueType: 'calendar' (round-trips as the native DateValue) |
number | nuxtUIInputNumber |
boolean | nuxtUISwitch |
| array of primitives (or empty array) | nuxtUIInputTags |
| array of objects | nuxtUIRepeater (item fields inferred from the first element; newItem derived by blanking it) |
| nested plain object | FormKit group with inferred children |
null / undefined / functions / other object types | skipped (use an override to add the field explicitly) |
Labels are humanized from key names: firstName becomes First Name, email_address becomes Email Address.
An inferred nuxtUIRepeater ships usable out of the box: displayCloneButton/displayDeleteButton are enabled (both are opt-in on nuxtUIRepeater itself, hidden unless explicitly turned on) and buttonGroupClass is set to 'flex gap-2 justify-end' so the per-row action buttons lay out horizontally, right-aligned. Override any of the three per field to change this:
overrides: {
contacts: { displayCloneButton: false, buttonGroupClass: 'flex gap-1' },
}2
3
Overrides
The overrides prop adjusts, replaces, adds, or removes inferred fields. Keys are dot-paths into your data; values are either a partial schema node or false.
<FUAutoForm
:data="user"
:overrides="{
'firstName': { validation: 'required' },
'bio': { $formkit: 'nuxtUITextarea' },
'internalId': false,
'contacts.email': { validation: 'required|email' },
'nickname': { placeholder: 'Optional nickname' },
}"
/>2
3
4
5
6
7
8
9
10
- Merge semantics are a per-node shallow merge:
{ ...inferredNode, ...overrideNode }. Override keys win wholesale — keep the inferred$formkit/labeland addvalidation, or supply$formkitto swap the input type entirely. Supplyingchildrenreplaces the inferred children completely (no deep merge). falseremoves an inferred field.- Dot-paths reach nested fields:
address.citytargets a group child;contacts.emailtargets a repeater's item template (no index segment — overrides apply to every row). - Unknown paths append new fields at that level, defaulting to
$formkit: 'nuxtUIInput'with a humanized label. This also un-skips fields whose value wasnull.
Valibot Schemas
If you already describe your data with Valibot, pass the schema instead — the inputs, required/email/url/length/min/max validation rules, and nesting come from the schema's own structure. Valibot is introspected structurally; this module does not depend on it.
<template>
<FUAutoForm
:data="user"
:valibot-schema="schema"
/>
</template>
<script setup lang="ts">
import * as v from 'valibot'
const schema = v.object({
name: v.pipe(v.string(), v.minLength(2)),
email: v.pipe(v.string(), v.email()),
age: v.optional(v.pipe(v.number(), v.minValue(18))),
newsletter: v.boolean(),
contacts: v.array(v.object({ email: v.pipe(v.string(), v.email()) })),
})
const user = ref({ name: '', email: '', age: 18, newsletter: false, contacts: [] })
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- Non-optional entries get
required(except booleans — a plainv.boolean()must stay switchable tofalse). v.optional/v.nullable/v.nullishentries are not required; an optional's default value seeds new repeater rows.- Unsupported constructs (
picklist,union,record, ...) are skipped — add them viaoverrides. - When
valibot-schemais set it drives the schema;dataremains the value source.
Zod Schemas
If you already describe your data with Zod, pass the schema instead — same rules apply as Valibot above (required/email/url/length/min/max, nesting, skipped constructs). Zod is introspected via its own public schema getters (.type, .shape, .unwrap(), .minLength, .format, ...) rather than its internal _zod.def; this module does not depend on it.
<template>
<FUAutoForm
:data="user"
:zod-schema="schema"
/>
</template>
<script setup lang="ts">
import { z } from 'zod'
const schema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().min(18).optional(),
newsletter: z.boolean(),
contacts: z.array(z.object({ email: z.string().email() })),
})
const user = ref({ name: '', email: '', age: 18, newsletter: false, contacts: [] })
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- Non-optional entries get
required(except booleans, same reasoning as Valibot). .optional()/.nullable()/.nullish()entries are not required; a.default(...)seeds new repeater rows regardless of where it sits in an.optional()/.default()chain.- Both
.string().email()chains and the v4 top-levelz.email()shorthand are recognized. - Unsupported constructs (
enum,union,record, ...) are skipped — add them viaoverrides. - When
zod-schemais set it drives the schema;dataremains the value source. If bothvalibot-schemaandzod-schemaare passed, Valibot wins.
Re-Inference Contract
The schema is re-inferred only when the data, overrides, valibot-schema, or zod-schema prop references change — never while typing. Mutating the bound object does not rebuild the form (rebuilding would re-render the schema and drop input focus); replace the object to re-infer.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | Object | null | Data object to infer from; also seeds the form value (v-model supported) |
overrides | AutoFormOverrides | undefined | Dot-path keyed map of partial schema nodes or false |
valibotSchema | Object | undefined | Valibot object schema to derive inputs/validation from |
zodSchema | Object | undefined | Zod object schema to derive inputs/validation from |
All other props, events (@data-saved, @on-reset), and slots are passed through to FUDataEdit unchanged — submit-label, show-reset, debug-data, debug-schema, and so on.
Programmatic Use
The inference utilities are auto-imported and usable standalone — for example to tweak the inferred schema before handing it to FUDataEdit yourself:
const { inferFormSchema, inferFormSchemaFromValibot, inferFormSchemaFromZod } = useFormKitAutoForm()
const schema = inferFormSchema(user, { email: { validation: 'required|email' } })2
3