Skip to content

Dynamic Forms

Dynamic array fields

Schema definition

ts
const schema: FieldDefinition[] = [
  {
    type: 'array',
    name: 'members',
    label: 'Team members',
    fields: [
      { type: 'text', name: 'name', label: 'Name', required: true },
      { type: 'email', name: 'email', label: 'Email', required: true },
    ],
  },
]

Sub-field names are bare ('name', not 'members.name') — useFieldArray prefixes them to 'members.0.name', 'members.1.name', ... per row itself; a pre-prefixed name would end up doubled ('members.0.members.name').

FormRenderer renders an ArrayField automatically with Add / Remove buttons.

useFieldArray composable

ts
import { useFieldArray } from '@macrulez/vue-form-schema'

const { rows, count, append, prepend, remove, move, swap, replace } = useFieldArray(form, 'members')
MethodDescription
append(defaults?)Add a row at the end
prepend(defaults?)Add a row at the beginning
remove(index)Remove a row
move(from, to)Move a row
swap(a, b)Swap two rows
replace(index, defaults?)Replace a row with fresh defaults

rows is a ComputedRef<FieldArrayRow[]>. Each row exposes index, key, and fields — the nested FieldDefinition[] with prefixed paths for that row.

Multi-step forms

ts
import { useMultiStepForm } from '@macrulez/vue-form-schema'

const wizard = useMultiStepForm(
  [
    { title: 'Account', schema: accountFields },
    { title: 'Profile', schema: profileFields },
    { title: 'Confirm', schema: confirmFields },
  ],
  async (allValues) => {
    await api.register(allValues)
  },
)
Property / MethodDescription
currentStepRef<number> — 0-based index
totalStepsNumber of steps
isFirstStep / isLastStepComputedRef<boolean>
formUseFormReturn for the current step
valuesAll values across all steps merged
next()Validate current step then advance (returns false if invalid)
back()Go to previous step
goTo(n)Jump to step n
submit()Validate all steps then call onSubmit

MultiStepFormRenderer

vue
import { MultiStepFormRenderer } from '@macrulez/vue-form-schema/ui'

<MultiStepFormRenderer :wizard="wizard" />

Renders the current step's fields and Back / Next / Submit navigation buttons.

Field transform & parse

ts
const schema: FieldDefinition[] = [
  {
    type: 'text',
    name: 'username',
    // trim on every keystroke
    transform: (value) => (typeof value === 'string' ? value.trim() : value),
  },
  {
    type: 'text',
    name: 'tags',
    defaultValue: 'vue,react',
    // split into array at submit time — values.tags is still a string
    parse: (raw) =>
      String(raw)
        .split(',')
        .map((s) => s.trim()),
  },
]

transform mutates values immediately. parse runs only at submit time and does not mutate values.

File upload field

ts
import { fileType, fileSize, fileCount } from '@macrulez/vue-form-schema'

const schema: FieldDefinition[] = [
  {
    type: 'file',
    name: 'avatar',
    label: 'Profile photo',
    accept: 'image/*',
    validators: [
      fileType(['image/'], 'Only images are accepted'),
      fileSize(2 * 1024 * 1024, 'Max 2 MB'),
    ],
  },
  {
    type: 'file',
    name: 'attachments',
    label: 'Attachments',
    multiple: true,
    validators: [fileCount(5, 'Up to 5 files')],
  },
]

values['avatar'] is File | null; values['attachments'] is File[] | null.

FormRenderer automatically renders FileField with a drag-and-drop zone and a file list with remove buttons.