← Blog
5 Sept 2026VueVue formsDynamic formsSchema-driven formsForm validation

Dynamic Vue Forms: Patterns for Adaptive, Validated Interfaces

Learn how to design dynamic Vue forms with schema-driven fields, conditional logic, repeatable data, accessible controls, and layered validation.

Dynamic Vue Forms: Patterns for Adaptive, Validated Interfaces

Dynamic Vue forms are interfaces whose fields, sections, and rules can change from a clear form definition and the user’s current data. Instead of hard-coding every input for every possible workflow, we define what the form can render, decide which parts are active, and keep the submitted model predictable.

This approach is valuable for onboarding flows, account settings, product configuration, admin tools, and any interface where permissions, a selected plan, or repeatable records alter the fields a user needs to see. It is not a reason to make every simple contact form schema-driven. We use dynamic rendering when variation is real, repeated, and worth centralizing.

Table of contents

What makes a Vue form dynamic?

A form becomes dynamic when one or more parts of its interface are determined at runtime. That can include:

  • Conditional fields: selecting a paid plan reveals billing details.
  • Repeatable groups: an admin can add or remove invitees, each with an email and role.
  • Data-driven controls: select options arrive from an API or product configuration.
  • Permission-aware editing: admins can change a field that ordinary users can only read.
  • Schema-driven rendering: a definition maps data types and constraints to Vue components.

Vue’s v-model provides a practical basis for this work because it keeps a control and its JavaScript state synchronized. The important design decision is what we make the source of truth: a stable data model, rather than a scattered set of component-local values.

The four layers we keep separate

Dynamic forms stay maintainable when we separate the definition, the model, the renderer, and runtime state.

  1. Form definition: field names, data types, labels, constraints, UI hints, and visibility rules.
  2. Form model: the values a user is editing and the API expects to receive.
  3. Renderer: Vue logic that maps allowed field types to allowed components.
  4. Runtime state: touched status, validation errors, loading state, server feedback, and temporary visibility.

The definition answers, “What can this form contain?” The model answers, “What did the user enter?” Mixing the two makes every later change more expensive. A schema should not accumulate live server errors, and a submitted payload should not include component names or layout instructions.

Diagram showing a form definition and UI metadata flowing to a dynamic form and validated submitted data

This split is especially useful when a contract needs to be portable. JSON Schema can represent data shape and validation-oriented constraints, while annotations such as titles and descriptions provide context to tools that generate documentation or forms. We can then add UI-specific metadata at the rendering boundary instead of coupling an API contract to one component library.

For DOM Studio projects, the Schema reference describes this distinction as definition data, form data, a portable JSON Schema contract, and optional persistence metadata.

A practical dynamic form definition

We recommend a small, explicit field definition rather than an open-ended remote component tree. The renderer should choose from a known component map, and the server should still enforce any authorization or business rule.

<script setup>
import { computed, ref } from 'vue';
import {
  DomEmailInput,
  DomForm,
  DomSelect,
  DomTextInput,
} from '@getdom/studio/vue';

const model = ref({
  plan: 'starter',
  billingEmail: '',
  companyName: '',
});

const controls = {
  text: DomTextInput,
  email: DomEmailInput,
  select: DomSelect,
};

const fields = [
  {
    id: 'plan',
    name: 'plan',
    kind: 'select',
    label: 'Plan',
    props: {
      options: [
        { label: 'Starter', value: 'starter' },
        { label: 'Team', value: 'team' },
        { label: 'Enterprise', value: 'enterprise' },
      ],
    },
  },
  {
    id: 'companyName',
    name: 'companyName',
    kind: 'text',
    label: 'Company name',
    props: { required: true },
  },
  {
    id: 'billingEmail',
    name: 'billingEmail',
    kind: 'email',
    label: 'Billing email',
    when: (values) => values.plan !== 'starter',
    props: { required: true },
  },
];

const activeFields = computed(() =>
  fields.filter((field) => !field.when || field.when(model.value)),
);
</script>

<template>
  <DomForm v-model="model" class="space-y-4">
    <component
      :is="controls[field.kind]"
      v-for="field in activeFields"
      :key="field.id"
      :name="field.name"
      :label="field.label"
      v-bind="field.props"
    />
  </DomForm>
</template>

This example has a deliberate boundary: the controls object is an allowlist. We do not accept an arbitrary component name from an API and render it unchecked. The definition can be server-provided, but the client should map trusted field types to reviewed components.

The stable :key matters too. When a conditional field appears, disappears, or moves, a durable field identity helps Vue preserve the intended component lifecycle instead of reusing state for the wrong control.

Conditional fields should follow business meaning

A dynamic form is not simply a collection of v-if statements. We get better results when every rule has a clear place in the definition and a clear relationship to the underlying data.

For example, a selected plan may reveal billing contact information. An isContractor checkbox may reveal an end date. A shipping method may introduce a customs section. Each of those rules should state:

  • Which values control visibility.
  • Whether a newly visible field is required.
  • What happens to its value when it becomes hidden.
  • Whether the API accepts the field in the current scenario.

The last point prevents a common bug: a user selects a plan, enters a billing email, switches back, and submits a payload containing stale billing data. Decide intentionally whether hidden data should be retained, cleared, or ignored at submission time.

Dynamic application form revealing nested fields and repeatable invitee rows

For ordinary fields, use semantic native input types and real labels. A dynamic renderer must not trade away accessibility for flexibility. Labels, descriptions, error relationships, keyboard behavior, and sensible button types remain necessary even when the controls are generated from configuration.

Repeatable groups need nested data, not invented field names

Repeatable data is where dynamic Vue forms show their value. Instead of creating fields such as invitee1Email and invitee2Email, keep the model faithful to the domain:

const model = ref({
  invitees: [
    { email: '', role: 'member' },
  ],
});

The renderer can add and remove complete rows, while each row remains an object with a stable shape. A field path such as invitees.0.email can be validated and targeted by server feedback without parsing custom names.

This model also supports good user experience decisions. Give each row an explicit remove action, ensure the action is a non-submit button, and keep focus predictable after an item is added or removed. For complex lists, use stable record IDs rather than an array index as the rendering key when users can reorder items.

DOM Studio’s Form reference supports named child fields, nested paths, form-level validation, and programmatic field state. That lets us keep the data in one parent model while individual controls participate in the same form lifecycle.

Validation belongs at more than one layer

Dynamic rendering and validation solve different problems. We use both.

  • Field constraints cover simple requirements such as presence, format, length, and range.
  • Form rules cover relationships, such as a billing email being required for a paid plan or two password fields matching.
  • Server validation enforces authorization, uniqueness, pricing, inventory, and data integrity.

The browser and client can provide fast, specific feedback, but the server remains the authority before data is saved. If the server rejects a value, return the error to the specific field path rather than inserting it into the durable field definition.

When repeated forms call for schema validation, VeeValidate and Zod are useful validation-focused tools to evaluate. Vueform and SurveyJS are broader form frameworks to consider when a team needs their particular rendering, builder, or survey capabilities. The best fit depends on whether we need editable UI primitives, validation composition, a visual builder, or a fully managed form experience.

For a complementary overview of Vue 3 validation with VeeValidate and Zod, watch this independent video:

When schema-driven forms are the right next step

We do not need a formal schema for every form. A hand-authored Vue component is usually clearer when the flow is short, fixed, and unique. Move toward schema-driven forms when at least one of these conditions is true:

  • The product repeats the same field structures across multiple routes.
  • A backend, CMS, or database already defines much of the data shape.
  • Admins or product teams need to configure form behavior without duplicating component code.
  • The data contract must serve validation, test fixtures, documentation, and UI generation.
  • Field types, conditional rules, or nested structures would otherwise be copied in several places.

At that point, start with the smallest useful contract. Use primitive types, required fields, enums, and nested objects first. Add UI decoration only when a standard mapping is not enough. For example, a pure data schema can represent an enum, while the UI layer supplies option labels, help text, or a richer control choice.

Our schema-driven forms guide shows this pattern with JSON Schema and DOM Studio adapters, including nested objects, repeatable rows, validation, and a raw JSON mode for technical workflows.

Common dynamic Vue form mistakes

Treating the schema as live form state

Do not write current values, touched flags, server errors, or loading states into a reusable form definition. Those belong to the model or runtime state.

Rendering untrusted component names

Remote configuration can be helpful, but it needs a field type allowlist and validated props. Do not make arbitrary dynamic components executable through form JSON.

Losing values without a rule

When a field is hidden, decide whether its value persists, resets, or is excluded from submission. Make that behavior testable.

Reusing unstable keys in repeaters

An array index is fragile when rows can be reordered or removed. Prefer a stable ID supplied by the data model.

Relying on client validation alone

Client validation improves feedback. It cannot authorize a request or protect the integrity of stored data.

Using a dynamic form where a static component is clearer

A dynamic system has a cost: definitions, mappings, rendering rules, tests, and governance. Use it where it reduces real duplication, not merely because it feels more flexible.

How we test dynamic forms

Dynamic forms need behavior-focused tests across all four layers. We test representative definitions and values, including the unhappy paths:

  • Selecting a value reveals the correct conditional fields.
  • Hidden fields retain, reset, or omit values according to the product rule.
  • A repeated row adds the expected nested object and removes it safely.
  • Every generated field has an associated label and meaningful validation feedback.
  • A change to the definition does not alter the expected submission shape unexpectedly.
  • Server errors return to the correct nested field path.
  • Unsupported field types and malformed configuration fail safely.

Vue Test Utils is a practical companion for testing form input behavior, including setting values and triggering interaction in component tests.

Build adaptive forms without giving up control

Dynamic Vue forms work best when we treat them as a governed rendering system, not a collection of clever template conditions. Keep the data model stable, define fields declaratively, render from an allowlist, model repeatable structures as arrays and objects, and validate on both the client and server.

If we are building repeated onboarding, settings, or administration flows, DOM Studio gives us editable Vue form primitives, a form provider with nested paths and runtime errors, plus adapters that turn Zod-like objects or JSON Schema into renderable children. Start by standardizing one repeated form pattern, then use that contract to make the next form faster and more consistent.

For the component-level foundation, read our Vue form components guide.