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.
Inhaltsverzeichnis
- 1. Das Konzept konfigurationsgetriebener Formulare
- 2. Felddefinitionen als JSON-Schema
- 3. Dynamische Feldkomponenten mit component :is
- 4. v-model-Handling im Form Builder
- 5. Bedingte Felder und Abhängigkeiten
- 6. Dynamische Validierungsregeln
- 7. Array-Felder und Wiederholgruppen
- 8. Formularschema vom Server laden
- 9. Vergleich: Form Builder Ansätze
- 10. Zusammenfassung
- 11. FAQ
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.