---
title: "Reactive & Conditional"
description: "Dynamically show or hide fields based on form state to create smart reactive forms."
canonical_url: "https://nuxt.mhaibaraai.cn/en/docs/auto-form/reactive"
---
# 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`](https://nuxt.mhaibaraai.cn/docs/auto-form/slots#autoformfieldcontext) (containing `state`, `path`, `value`). The function re-evaluates automatically as the form state changes, enabling conditional rendering and field dependencies.

> [!NOTE]
> 
> There are two ways to conditionally render, with different behaviors:
> 
> - **if** - Display condition; when `false` the field is **not created** (not mounted, not included in validation).
> - **hidden** - Whether to hide; when `true` the 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.

> [!TIP]
> 
> 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.

```vue [AutoFormReactiveConditionalExample.vue]
<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.

> [!NOTE]
> 
> Field dependencies only re-evaluate control attributes and will not automatically clear old values. Use 
> 
> watch
> 
>  to manually reset stale options when needed.

```vue [AutoFormReactiveDependentExample.vue]
<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>
```

> [!NOTE]
> 
> When `items` comes from **async or external reactive data** (e.g. a `computed` / `ref` based on `useApiFetch` or `useAsyncData`), always use **functional** `controlProps: () => ({ 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` |

> [!TIP]
> 
> 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.


## Sitemap

See the full [sitemap](https://nuxt.mhaibaraai.cn/sitemap.md) for all pages.
