Skip to content

DynamicForm ​

DynamicForm is the component you add to a <form> element. It reads the metadata array, builds a field tree, and renders each field through your template.

Props ​

PropTypeRequiredDescription
templateComponentYesYour DynamicFormTemplate SFC
metadataFieldMetadata | FieldMetadata[]YesOne field definition or an array of field definitions
settingsDynamicFormSettingsNoValidation behavior and message overrides

Basic Usage ​

vue
<template>
  <form @submit.prevent="onSubmit">
    <DynamicForm
      :template="MyFormTemplate"
      :metadata="fields"
      :settings="settings"
    />
    <button type="submit">Submit</button>
  </form>
</template>

useDynamicForm() ​

useDynamicForm() is an optional typed wrapper around vee-validate's useForm(). You can use useForm() directly if you prefer — useDynamicForm() just adds two extra helpers on top:

ts
const {
  handleSubmit,
  values,
  errors,
  meta,
  setValues,
  resetForm,
  setErrors,
  useFieldValue,    // typed path-aware field value accessor
  validateSection,  // validates only the fields under a given path
} = useDynamicForm<MyFormValues>();

useFieldValue(path) is a typed helper for reading a specific field value reactively. It is particularly useful inside computedProps:

ts
const country = useFieldValue('address.country');

validateSection(path) validates only the vee-validate fields registered under the given path prefix, returning the same FormValidationResult shape as validate(). See useValidatePartialForm for the full API and a wizard example.

Options ​

useDynamicForm() accepts an optional FormOptions object forwarded directly to vee-validate's useForm():

OptionTypeDefaultDescription
initialValuesPartialDeep<TValues> | nullundefinedPre-populate form fields on mount
initialErrorsRecord<path, string | undefined>undefinedSet initial validation errors by field path
initialTouchedRecord<path, boolean>undefinedSet initial touched state by field path
validateOnMountbooleanfalseRun validation immediately when the form mounts
keepValuesOnUnmountboolean | Ref<boolean>falsePreserve form values when the component unmounts
namestringundefinedNamed form — used to scope nested forms

A per-field fieldOptions.keepValueOnUnmount overrides keepValuesOnUnmount for that one field. Attribute fields (a field's attributes entries) clear on unmount when the owning field's own value is gone (an empty complex value keeps no stale attributes); when the owning field still has a value, its attributes follow the same keepValuesOnUnmount / fieldOptions.keepValueOnUnmount rules as any other field.

ts
const form = useDynamicForm<MyFormValues>({
  initialValues: { email: 'prefilled@example.com' },
  validateOnMount: true,
});

Typed Settings ​

When your metadata declares extended settings properties, use GetDynamicFormSettingsType to derive the fully typed settings object:

ts
import type { GetDynamicFormSettingsType } from '@bach.software/vue-dynamic-form';
import { metadata } from './MyFormTemplate.vue';

type FormSettings = GetDynamicFormSettingsType<typeof metadata>;

const settings: FormSettings = {
  showRequiredOrOptional: 'optional', // typed from ExtendedSettingsProperties
  validateOnBlur: true,               // always available from DynamicFormSettings
};

The settings object is injected into every template slot as the settings prop, so templates can react to it without threading values through field metadata. See defineMetadata — ExtendedSettingsProperties for how to declare extended settings.

Settings Reference ​

Pass a DynamicFormSettings object via :settings to control when validation runs and what messages are shown.

Validation Timing ​

validateOnValueUpdate ​

Type: boolean | Default: true

When true, validation runs every time a field value changes. Set to false to validate only on submit.

ts
{ validateOnValueUpdate: false }

validateOnValueUpdateAfterSubmit ​

Type: boolean | Default: false

When true, live validation activates only after the user's first submit attempt. Before that first submit, validation is deferred. This is a common UX pattern — don't show errors until the user tries to submit.

Defaults to false to stay consistent with vee-validate's out-of-the-box behavior.

ts
{ validateOnValueUpdateAfterSubmit: true }

Recommended combination

validateOnValueUpdate: false + validateOnValueUpdateAfterSubmit: true gives the cleanest UX: no errors on first fill-in, live feedback after the first submit.

validateOnBlur ​

Type: boolean | Default: false

When true, validation runs when the user leaves a field (blur event). Works alongside validateOnValueUpdate.

ts
{ validateOnBlur: true }

validateWhenInError ​

Type: boolean | Default: true

When true, a field that already has an error validates live on every value change, regardless of the other timing settings. Gives immediate feedback when fixing a known error.

ts
{ validateWhenInError: true }

Message Overrides ​

messages ​

Override the default error text for the validation rules from FieldMetadata.restriction and occurrence constraints (minOccurs, choice). Each entry accepts a string template or a function.

String templates support these placeholders:

PlaceholderAvailable in
{field}All rules — the field's label
{0}, {1}, …All rules — rule params by position
{min}minOccurs, choiceMinOccurs, minInclusive, minExclusive
{max}maxInclusive, maxExclusive
{length}minLength, maxLength, length
{pattern}pattern
{mode}whiteSpace
{digits}fractionDigits, totalDigits

Function form receives vee-validate's ValidationMessageContext:

ts
messages: {
  required: (ctx) => `${ctx.field} cannot be empty`,
}

Available Message Keys ​

KeyDefault triggerExample message
requiredField has no value and minOccurs >= 1'{field} is required'
minOccursArray has fewer items than minOccurs'At least {min} items required'
choiceMinOccursChoice has fewer filled branches than minOccurs [^1]'Select at least {min} option in {field}'
minLengthValue shorter than restriction.minLength'{field} must be at least {length} characters'
maxLengthValue longer than restriction.maxLength'{field} may be at most {length} characters'
lengthValue length does not equal restriction.length'{field} must be exactly {length} characters'
patternValue doesn't match restriction.pattern'{field} does not match the expected format'
minInclusiveValue below restriction.minInclusive'{field} must be at least {min}'
maxInclusiveValue above restriction.maxInclusive'{field} must be at most {max}'
minExclusiveValue at or below restriction.minExclusive'{field} must be greater than {min}'
maxExclusiveValue at or above restriction.maxExclusive'{field} must be less than {max}'
enumerationValue not in restriction.enumeration'{field} must be one of the allowed values'
whiteSpaceValue violates restriction.whiteSpace mode'{field} contains invalid whitespace'
fractionDigitsValue has more decimal places than restriction.fractionDigits'{field} may have at most {digits} decimal places'
totalDigitsValue has more significant digits than restriction.totalDigits'{field} may have at most {digits} digits'

[^1]: Choice fields don't exist as entries in vee-validate's value tree, so the xsd_choiceMinOccurs rule itself always returns false. DynamicFormTemplate detects when no branch is filled and surfaces the message directly — the message setting still controls the text shown.

complexTypeValueProperty ​

Type: string | Default: 'value'

When a field has attributes or isComplexType: true, the form value is stored as an object: { value: '...' }. This setting changes the property name from value to something else:

ts
{ complexTypeValueProperty: 'text' }
// Field value becomes: { text: 'the actual value', ...attributes }

analytics ​

Type: boolean | Default: false

When true, enables hidden DOM elements that expose render counters for each field. Used for testing that reactivity optimisations are working correctly. Not intended for production.

Full Settings Example ​

ts
import type { DynamicFormSettings } from '@bach.software/vue-dynamic-form';

const settings: DynamicFormSettings = {
  // Validate live only after first submit, then re-validate on blur and error
  validateOnValueUpdate: false,
  validateOnValueUpdateAfterSubmit: true,
  validateOnBlur: true,
  validateWhenInError: true,

  messages: {
    required:          '{field} is required',
    minOccurs:         'Add at least {min} item(s) to {field}',
    choiceMinOccurs:   'Choose at least {min} option(s) for {field}',
    minLength:         '{field} must be at least {length} characters',
    maxLength:         '{field} may not exceed {length} characters',
    length:            '{field} must be exactly {length} characters',
    pattern:           '{field} format is invalid',
    minInclusive:      '{field} must be {min} or more',
    maxInclusive:      '{field} must be {max} or less',
    minExclusive:      '{field} must be greater than {min}',
    maxExclusive:      '{field} must be less than {max}',
    enumeration:       '{field} must be one of the allowed values',
    whiteSpace:        '{field} contains invalid whitespace',
    fractionDigits:    '{field} may have at most {digits} decimal place(s)',
    totalDigits:       '{field} may have at most {digits} significant digit(s)',
  },
};

Released under the MIT License.