Form Builder und dynamische Formulare in Vue 3
AI generated
<v/>
{ }
Vue 3 · Form Builder · Dynamische Formulare · JSON-Schema
Form Builder und dynamische
Formulare in Vue 3

Formulare, die sich aus Konfiguration generieren, statt hart codiert zu sein, sparen Entwicklungszeit und ermöglichen Back-End-Teams, Formularstrukturen ohne Frontend-Deployment zu ändern. Dieser Artikel zeigt, wie man einen robusten Form Builder in Vue 3 baut – von der Felddefinition über dynamische Validierung bis zu bedingten Feldern.

14 Min. Lesezeit component :is · JSON-Schema · bedingte Felder · Validierung Vue 3.4+ · Composition API

1. Das Konzept konfigurationsgetriebener Formulare

Ein Form Builder in Vue 3 verschiebt die Formularstruktur aus dem Template in eine Datenkonfiguration. Statt für jeden Anwendungsfall ein neues Formular-Template zu schreiben, definiert man die Felder, ihre Typen, Labels, Standardwerte und Validierungsregeln in einem JavaScript-Objekt oder JSON-Dokument. Die Vue-Komponente liest diese Konfiguration und rendert daraus das entsprechende Formular – vollständig reaktiv und ohne hartkodierte Felder im Template.

Der entscheidende Vorteil dieses Ansatzes liegt in der Skalierbarkeit. Ein Back-End-Team kann neue Formularfelder über eine API ausliefern, ohne dass Frontend-Entwickler das Template anpassen müssen. Ein CMS kann Formularstrukturen für Landing Pages konfigurieren. A/B-Tests können verschiedene Feldanordnungen oder -typen ausliefern, ohne Code-Deployments. Für Anwendungen mit vielen Formularvarianten – etwa Checkout-Formulare mit länderspezifischen Feldern oder Produktkonfigurationsformulare – ist der Form Builder Ansatz in Vue 3 erheblich effizienter als statische Templates.

2. Felddefinitionen als JSON-Schema

Das Fundament jedes dynamischen Formulars in Vue 3 ist ein klar definiertes Schema für die Felddefinitionen. Jede Felddefinition ist ein Objekt mit mindestens den Eigenschaften type (z.B. "text", "select", "checkbox"), name (eindeutiger Feldbezeichner für das Formularmodell), label (Anzeigename) und optional placeholder, defaultValue, rules (Validierungsregeln) und options (für Select/Radio). Für bedingte Felder kommt showIf als Callback oder Konfigurationsobjekt hinzu, das auf andere Feldwerte referenziert.

Ein wichtiges Designprinzip beim Form Builder: Das Schema sollte JSON-serialisierbar sein, damit es vom Server geliefert werden kann. Das schließt Funktionen als Werte für Callback-basierte Bedingungen aus – stattdessen definiert man Bedingungen als deklarative Objekte, die der Form Builder interpretiert. Das Schema-Format sollte außerdem erweiterbar sein: Neue Feldtypen kommen durch die Registrierung neuer Feldkomponenten hinzu, ohne das Schema-Format zu ändern. Dieser Ansatz hält das dynamische Formular-System in Vue 3 langfristig wartbar.


// form-schema.js — field definitions for a dynamic Vue 3 form
export const registrationFormSchema = [
  {
    type: 'text',
    name: 'firstName',
    label: 'Vorname',
    placeholder: 'Max',
    rules: { required: true, minLength: 2 },
  },
  {
    type: 'select',
    name: 'country',
    label: 'Land',
    defaultValue: 'DE',
    options: [
      { value: 'DE', label: 'Deutschland' },
      { value: 'AT', label: 'Österreich' },
      { value: 'CH', label: 'Schweiz' },
    ],
    rules: { required: true },
  },
  {
    type: 'text',
    name: 'vatId',
    label: 'USt-IdNr.',
    // Conditional: only show when country is AT or CH
    showIf: { field: 'country', operator: 'in', value: ['AT', 'CH'] },
    rules: { required: false, pattern: '^[A-Z]{2}[0-9A-Z]+$' },
  },
  {
    type: 'checkbox',
    name: 'newsletter',
    label: 'Newsletter abonnieren',
    defaultValue: false,
  },
]

3. Dynamische Feldkomponenten mit component :is

Das Herzstück des Form Builders in Vue 3 ist die component :is-Direktive, die es erlaubt, zur Laufzeit zu entscheiden, welche Komponente für ein Feld gerendert wird. Man registriert alle Feldtypen in einer Map, die vom Feldtyp-String auf die entsprechende Vue-Komponente zeigt. Der Form Builder iteriert über das Schema und rendert für jede Felddefinition die passende Komponente via :is="fieldComponents[field.type]". Unbekannte Typen können auf eine generische Fallback-Komponente abgebildet werden, die eine Warnung anzeigt.

Jede Feldkomponente erhält die Felddefinition als Prop und implementiert v-model über defineModel() für nahtlose bidirektionale Datenbindung. Das Design dieser Schnittstelle ist kritisch für die Erweiterbarkeit: Wer neue Feldtypen hinzufügen will, muss nur eine neue Komponente erstellen und in der Map registrieren, ohne den Form Builder selbst zu ändern. Dieses Open/Closed-Prinzip macht den dynamischen Form Builder zu einer echten Architekturkomponente, nicht nur einem Template-Trick.

4. v-model-Handling im Form Builder

Das v-model-Handling im Form Builder muss den gesamten Formularzustand als reaktives Objekt verwalten, das für jedes Feld im Schema einen Schlüssel enthält. Beim ersten Rendern wird das Modell aus den defaultValue-Angaben des Schemas initialisiert. Felder, die kein defaultValue definieren, werden mit undefined oder dem typspezifischen Standardwert vorbelegt. Der Form Builder leitet Änderungen von Feldkomponenten via update:modelValue-Events an das übergeordnete Formularmodell weiter und gibt das gesamte aktualisierte Modell nach oben.

In Vue 3.4+ vereinfacht defineModel() dieses Muster erheblich. Die Feldkomponenten definieren ihr eigenes Model mit const value = defineModel() und können direkt damit arbeiten, ohne manuelle Prop/Emit-Boilerplate. Der Form Builder selbst nutzt ein internes reactive()-Objekt als Zustandsspeicher und propagiert Änderungen gebündelt nach oben. Dieses Muster ist wichtig für Formulare mit hunderten Feldern, wo viele kleine emit('update:modelValue')-Aufrufe zu Performance-Problemen führen könnten.


<!-- FormBuilder.vue — dynamic form renderer with component :is -->
<template>
  <form @submit.prevent="handleSubmit">
    <template v-for="field in visibleFields" :key="field.name">
      <div class="form-field-wrapper">
        <label :for="field.name">{ { field.label } }</label>
        <component
          :is="fieldComponents[field.type] ?? FallbackField"
          :id="field.name"
          :field="field"
          v-model="formData[field.name]"
          :error="errors[field.name]"
        />
        <span v-if="errors[field.name]" class="field-error">
          { { errors[field.name] } }
        </span>
      </div>
    </template>
    <button type="submit" :disabled="isSubmitting">Absenden</button>
  </form>
</template>

<script setup>
import { reactive, computed } from 'vue'
import TextInput from './fields/TextInput.vue'
import SelectField from './fields/SelectField.vue'
import CheckboxField from './fields/CheckboxField.vue'
import FallbackField from './fields/FallbackField.vue'

const props = defineProps({ schema: Array, modelValue: Object })
const emit = defineEmits(['update:modelValue', 'submit'])

// Map field type strings to components
const fieldComponents = { text: TextInput, select: SelectField, checkbox: CheckboxField }

// Initialize reactive form data from schema defaults
const formData = reactive(
  Object.fromEntries(props.schema.map((f) => [f.name, f.defaultValue ?? null]))
)

// Evaluate showIf conditions to filter visible fields
const visibleFields = computed(() =>
  props.schema.filter((field) => {
    if (!field.showIf) return true
    const { field: depField, operator, value } = field.showIf
    const depValue = formData[depField]
    if (operator === 'in') return Array.isArray(value) && value.includes(depValue)
    if (operator === 'eq') return depValue === value
    return true
  })
)
<\/script>

5. Bedingte Felder und Abhängigkeiten

Bedingte Felder sind der Punkt, an dem ein Form Builder in Vue 3 seinen vollen Wert zeigt. Statt im Template mit v-if auf hart codierte Bedingungen zu prüfen, wertet der Form Builder die showIf-Konfiguration jedes Felds aus und blendet nicht zutreffende Felder aus. Wenn ein Feld ausgeblendet wird, muss sein Wert aus dem Formularmodell entfernt oder zurückgesetzt werden, damit es nicht fälschlicherweise übermittelt wird. Dieser Reset-on-hide-Mechanismus ist ein häufig übersehenes Detail, das in Produktionsformularen zu schwer debugbaren Validierungsfehlern führt.

Komplexere Abhängigkeiten zwischen Feldern – etwa "Feld B ist erforderlich, wenn Feld A mehr als 1000 enthält" – lassen sich durch ein erweitertes showIf-Format abbilden, das mehrere Bedingungen mit and oder or verknüpft. In Vue 3 nutzt man dafür computed() mit einer rekursiven Bedingungsauswertung. Das Muster bleibt dabei vollständig deklarativ – der dynamische Form Builder wertet die Bedingungen aus, ohne dass die Feldkomponenten voneinander wissen müssen.

6. Dynamische Validierungsregeln

Validierung in einem Form Builder muss ebenfalls konfigurationsgetrieben sein. Die Validierungsregeln im Schema – required, minLength, maxLength, pattern, min, max – werden von einer Validierungs-Engine ausgewertet, die eine Map von Regelname zu Validierungsfunktion pflegt. Das Ergebnis ist ein Fehler-Objekt mit Feldname als Schlüssel und Fehlermeldung als Wert, das reaktiv ins Template durchgeleitet wird. Jede Feldkomponente erhält das entsprechende Fehler-String als Prop und zeigt es unter dem Feld an.

Async-Validierungsregeln – etwa "ist diese E-Mail-Adresse bereits registriert?" – erfordern zusätzliche Sorgfalt: Sie sollten nur bei blur ausgelöst werden, nicht bei jedem Tastendruck, und müssen debounced sein, um unnötige API-Anfragen zu vermeiden. In Kombination mit VeeValidate oder Zod lassen sich die Validierungsregeln aus dem Schema in typsichere Validierungsschemata übersetzen, die sowohl Frontend- als auch Backend-Validierung konsistent halten. Dieser Ansatz macht das dynamische Formular-System in Vue 3 produktionsreif.

7. Array-Felder und Wiederholgruppen

Viele Formulare enthalten Wiederholgruppen – etwa "Kontaktpersonen hinzufügen" oder "Positionen in einer Bestellung". Ein Form Builder in Vue 3 muss dieses Muster unterstützen, ohne für jeden Array-Feldtyp eine separate Implementierung zu brauchen. Der Ansatz: Eine ArrayField-Komponente rendert eine Liste von Unterformularen, jedes mit derselben Feldkonfiguration, und stellt Buttons für Hinzufügen und Entfernen von Einträgen bereit. Jeder Eintrag im Array hat seinen eigenen reaktiven Zustand und seine eigene Validierung.

Das Schema für ein Array-Feld enthält zusätzlich die Eigenschaften type: "array", minItems, maxItems und itemSchema, das wiederum ein vollständiges Formularschema für jeden Eintrag enthält. Diese Verschachtelung kann theoretisch beliebig tief gehen, in der Praxis reichen zwei Ebenen für die meisten Anwendungsfälle. Wichtig: Beim Entfernen eines Eintrags müssen alle Validierungsfehler für diesen Eintrag ebenfalls gelöscht werden, um keine verwaisten Fehler im Fehlerzustand zu hinterlassen.


// composables/useFormValidation.js — dynamic validation engine
import { reactive } from 'vue'

const validators = {
  required: (value) => (value === null || value === '' || value === undefined)
    ? 'Dieses Feld ist erforderlich.' : null,
  minLength: (value, min) => value && value.length < min
    ? `Mindestens ${min} Zeichen erforderlich.` : null,
  maxLength: (value, max) => value && value.length > max
    ? `Maximal ${max} Zeichen erlaubt.` : null,
  pattern: (value, regex) => value && !new RegExp(regex).test(value)
    ? 'Das Format ist ungültig.' : null,
  min: (value, min) => value !== null && Number(value) < min
    ? `Mindestwert: ${min}.` : null,
  max: (value, max) => value !== null && Number(value) > max
    ? `Maximalwert: ${max}.` : null,
}

export function useFormValidation(schema, formData) {
  const errors = reactive({})

  function validateField(field) {
    if (!field.rules) return null
    for (const [rule, ruleValue] of Object.entries(field.rules)) {
      const fn = validators[rule]
      if (!fn) continue
      const error = fn(formData[field.name], ruleValue)
      if (error) {
        errors[field.name] = error
        return error
      }
    }
    delete errors[field.name]
    return null
  }

  function validateAll(visibleFields) {
    let valid = true
    for (const field of visibleFields) {
      if (validateField(field)) valid = false
    }
    return valid
  }

  return { errors, validateField, validateAll }
}

8. Formularschema vom Server laden

Ein Form Builder, der sein Schema nur aus statischen JavaScript-Dateien liest, nutzt sein Potenzial nicht vollständig. Der nächste Schritt ist das dynamische Laden des Schemas von einer API, sodass Back-End-Entwickler oder Content-Manager Formularstrukturen ohne Frontend-Deployment ändern können. In Nuxt 3 lädt man das Schema mit useFetch('/api/forms/registration') und übergibt es dann an den Form Builder. Das Formular rendert sich automatisch neu, wenn das Schema sich ändert, da alles reaktiv ist.

Beim Server-Side Rendering gibt es einen wichtigen Aspekt: Das Schema muss beim ersten Render bereits verfügbar sein, damit Suchmaschinen das Formular indexieren können und kein Layout-Shift beim Hydration entsteht. Mit useAsyncData und Server-Side Rendering ist das in Nuxt 3 automatisch gegeben. Ein weiteres wichtiges Detail: Das Server-Schema muss aus Sicherheitsgründen auf dem Server validiert werden, bevor es an den Client ausgeliefert wird – ein Schema, das die Validierungsregeln für sensible Felder entfernt oder bösartige Feldtypen einschleust, wäre ein Sicherheitsrisiko für das dynamische Formular-System.

9. Vergleich: Form Builder Ansätze in Vue 3

Es gibt mehrere Strategien, dynamische Formulare in Vue 3 zu implementieren. Die Wahl hängt von den Anforderungen an Flexibilität, Typsicherheit und Komplexität ab. Bibliotheken wie Formkit, VeeValidate und VueForms bieten eigene Form-Builder-Konzepte an, die sich in Ansatz und Zielgruppe unterscheiden.

Ansatz Stärken Schwächen Empfohlen für
Eigener Form Builder Volle Kontrolle, keine Abhängigkeiten Mehr Implementierungsaufwand Individuelle Feldtypen & Design-Systeme
Formkit Umfangreiche Feldtypen, gute DX Größeres Bundle, eigene Konventionen Schnelle Umsetzung mit Standardfeldern
VeeValidate + Schema Zod/Yup-Integration, typsicher Validierung-fokussiert, kein UI Komplexe Validierungsanforderungen
JSON-Schema-basiert Server-konfigurierbar, standardisiert Eingeschränkte dynamische Logik CMS-gesteuerte Formulare
Headless + Tailwind Volle Style-Kontrolle, schlank Kein vorgefertigtes Verhalten Design-System-Integration

Für die meisten Projekte empfiehlt sich ein hybrider Ansatz: ein eigener leichtgewichtiger Form Builder für die Rendering-Logik, kombiniert mit VeeValidate oder Zod für die Validierung. Das gibt volle Kontrolle über das Rendering und die UI, nutzt aber ausgereifte Bibliotheken für den komplexesten Teil – die Validierungslogik. So bleibt das dynamische Formular-System in Vue 3 wartbar, ohne auf bewährte Werkzeuge zu verzichten.

Mironsoft

Vue 3-Entwicklung, Form Builder und komplexe Formular-Systeme

Dynamische Formulare, die Back-End-Teams selbst konfigurieren können?

Wir entwickeln konfigurationsgetriebene Form Builder in Vue 3 – mit JSON-Schema, dynamischer Validierung, bedingten Feldern und nahtloser API-Integration, damit Formularstrukturen ohne Frontend-Deployment änderbar sind.

Schema-Design

JSON-Schema-basierte Felddefinitionen, die vom Server ausgeliefert und sicher validiert werden

Validierungslogik

Dynamische Validierungsregeln mit Zod-Integration und Async-Validierung für API-Checks

Design-System

Feldkomponenten im eigenen Tailwind-Design-System, vollständig barrierefreiheitskonform

10. Zusammenfassung

Ein Form Builder in Vue 3 ist kein Overkill, sondern eine Investition in Flexibilität und Wartbarkeit. Mit component :is für dynamische Feldkomponenten, einem klaren JSON-Schema für Felddefinitionen und einer konfigurierbaren Validierungs-Engine entstehen dynamische Formulare, die sich ohne Code-Änderungen anpassen lassen. Bedingte Felder durch deklarative showIf-Konfigurationen, Reset-on-hide für nicht sichtbare Felder und Array-Felder für Wiederholgruppen decken die häufigsten Anforderungen ab.

Das Schema vom Server zu laden öffnet die Tür für CMS-gesteuerte Formulare und Back-End-seitige Konfiguration. Kombiniert mit VeeValidate oder Zod für die Validierung und einer klaren Feldkomponenten-API bleibt das System erweiterbar: Neue Feldtypen kommen durch neue Komponenten, nicht durch Änderungen am Form Builder selbst. Dieser Ansatz skaliert von einfachen Kontaktformularen bis zu komplexen mehrstufigen Checkout-Formularen mit länderspezifischen Feldern – ohne Neuschreiben.

Form Builder in Vue 3 — Das Wichtigste auf einen Blick

Kern-Mechanismus

component :is mit einer Feldtyp-zu-Komponente-Map rendert dynamisch die richtige Feldkomponente – erweiterbar ohne Änderungen am Form Builder.

Bedingte Felder

Deklarative showIf-Konfiguration in der Felddefinition – mit Reset-on-hide, damit versteckte Felder nicht fälschlicherweise übermittelt werden.

Validierung

Regelbasierte Validierungs-Engine, die Schema-Regeln auswertet. Zod-Integration für typsichere, wiederverwendbare Validierungslogik.

Server-Schema

Schema via useFetch vom Server laden – CMS-gesteuerte Formulare ohne Frontend-Deployment. Schema serverseitig validieren.

11. FAQ: Form Builder und dynamische Formulare in Vue 3

1Was ist ein Form Builder in Vue 3?
Rendert Formularfelder aus einer JSON-Schema-Konfiguration via component :is – statt hart codierter Templates, erweiterbar durch neue Feldkomponenten.
2Wie funktioniert component :is?
Nimmt einen Komponentenverweis oder Namen und rendert die entsprechende Komponente. Im Form Builder mappt eine Map Feldtyp-Strings auf Komponenten.
3Bedingte Felder implementieren?
showIf-Konfiguration im Schema + computed() zum Filtern sichtbarer Felder + Reset-on-hide für versteckte Felder. Vollständig deklarativ.
4Validierungsbibliothek für dynamische Formulare?
VeeValidate + Zod-Adapter für typsichere Validierung. Für einfache Fälle reicht eine eigene Engine aus einfachen Validierungsfunktionen.
5Schema vom Server laden?
Ja, mit useFetch in Nuxt 3. Schema muss JSON-serialisierbar sein. Serverseitig validieren vor der Auslieferung, um Sicherheitsrisiken zu vermeiden.
6Array-Felder (Wiederholgruppen)?
ArrayField-Komponente mit eigenem itemSchema für jeden Eintrag. Beim Entfernen auch Validierungsfehler löschen. Maximal 2 Ebenen tief in der Praxis.
7v-model in Feldkomponenten?
defineModel() in Vue 3.4+ ist die einfachste Variante. Alternativ modelValue als Prop + update:modelValue emittieren. Gesamtzustand im reaktiven Objekt im Form Builder.
8Reset-on-hide bei bedingten Feldern?
Versteckte Felder auf Standardwert zurücksetzen, damit keine veralteten Werte übermittelt oder Validierungsfehler ausgelöst werden.
9Eigener Form Builder vs. Formkit?
Eigener Form Builder bei individuellem Design-System und spezifischen Feldtypen. Formkit für schnelle Standardlösungen mit höherem Bundle-Gewicht.
10Neue Feldtypen hinzufügen?
Neue Feldkomponente erstellen (modelValue Prop, update:modelValue Emit, field Prop). In der fieldComponents-Map unter neuem Typnamen registrieren.