Reactive & Conditional
Dynamically show or hide fields based on form state to create smart reactive forms.
Overview
All meta fields in AutoForm can accept reactive functions (ctx) => value, where ctx is the AutoFormFieldContext (containing state, path, value). The function re-evaluates automatically as the form state changes, enabling conditional rendering and field dependencies.
There are two ways to conditionally render, with different behaviors:
if- Display condition; whenfalsethe field is not created (not mounted, not included in validation).hidden- Whether to hide; whentruethe field remains in the DOM and form data, only visually hidden.
Examples
Conditional Rendering
if and hidden accept functions returning a boolean: when switching account type, business fields (if) are mounted/unmounted on demand; after checking the terms, the subscription field (hidden) switches from hidden to visible.
Use
if when you want to completely exclude a field (no validation, not submitted); use hidden when you want to temporarily hide it but retain its value.<script lang="ts" setup>
import type { FormSubmitEvent } from '@nuxt/ui'
import type { z } from 'zod'
import type { AutoFormFieldContext } from '@movk/nuxt'
const { afz } = useAutoForm()
const toast = useToast()
const schema = afz.object({
accountType: afz
.enum(['personal', 'business'])
.default('personal')
.meta({ label: '账户类型', description: '切换后观察 company/tax 字段的挂载与卸载' }),
username: afz
.string({ controlProps: { placeholder: '请输入用户名' } })
.min(3, '用户名至少需要 3 个字符')
.meta({ label: '用户名' }),
companyName: afz
.string({ controlProps: { placeholder: '请输入公司名称' } })
.meta({
label: '公司名称(if)',
hint: 'if:仅企业账户挂载,个人账户下字段不创建、不参与校验',
if: ({ state }: AutoFormFieldContext) => state?.accountType === 'business'
}),
taxId: afz
.string({ controlProps: { placeholder: '请输入税号' } })
.meta({
label: '税号(if)',
hint: 'if:与公司名称同条件挂载',
if: ({ state }: AutoFormFieldContext) => state?.accountType === 'business'
}),
agreeToTerms: afz
.boolean()
.default(false)
.meta({ label: '同意服务条款', description: '勾选后下方订阅字段显示' }),
newsletter: afz
.boolean()
.default(true)
.meta({
label: '订阅邮件通知(hidden)',
description: 'hidden:未同意条款时隐藏,但字段始终保留在 DOM 与表单数据中',
hidden: ({ state }: AutoFormFieldContext) => !state?.agreeToTerms
})
})
type Schema = z.output<typeof schema>
const form = ref<Partial<Schema>>({})
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({
title: '提交成功',
color: 'success',
description: JSON.stringify(event.data, null, 2)
})
}
</script>
<template>
<MAutoForm :schema="schema" :state="form" @submit="onSubmit" />
</template>
Dependent Fields
Functional controlProps re-evaluates as other fields change. In the example below, city options depend on the country field, and a watch clears the stale value when the country changes.
Field dependencies only re-evaluate control attributes and will not automatically clear old values. Use
watch to manually reset stale options when needed.<script lang="ts" setup>
import type { z } from 'zod'
const { afz } = useAutoForm()
const countryOptions = ['CN', 'US', 'JP'] as const
type CountryCode = typeof countryOptions[number]
const cityItemsByCountry: Record<CountryCode, string[]> = {
CN: ['北京', '上海', '广州'],
US: ['New York', 'San Francisco', 'Seattle'],
JP: ['Tokyo', 'Osaka', 'Kyoto']
}
const schema = afz.object({
country: afz.enum(countryOptions).default('CN').meta({ label: '国家' }),
city: afz.enum([], {
type: 'enum',
controlProps: ({ state }) => ({
items: cityItemsByCountry[(state.country as CountryCode | undefined) ?? 'CN']
})
}).meta({ label: '城市', hint: '选项随国家变化,切换国家后会清空已选城市' })
})
const state = reactive<Partial<z.output<typeof schema>>>({})
watch(() => state.country, () => {
state.city = undefined
})
</script>
<template>
<MAutoForm :schema="schema" :state="state" />
</template>
When
items comes from async or external reactive data (e.g. a computed / ref based on useApiFetch or useAsyncData), always use functionalcontrolProps: () => ({ items: source.value }), or pass the reactive source directly as items: source — never write items: source.value.The schema is only constructed once during setup, so source.value is a one-time snapshot at that moment. This may appear to work during SSR hydration when data is ready, but will freeze to an empty array when the component re-mounts during client-side navigation before async data arrives, and subsequent option updates will no longer take effect. Functional controlProps evaluates at render time and avoids this problem.API
meta
| Name | Type | Description |
|---|---|---|
if | ReactiveValue<boolean, AutoFormFieldContext> | Display condition; field is not created when false |
hidden | ReactiveValue<boolean, AutoFormFieldContext> | Whether to hide; retains DOM but visually hidden when true |
In addition to the above fields,
label, hint, description, controlProps, class and other meta fields also support the (ctx) => value function form, dynamically evaluated based on form state.