VUE

Dynamic Form Generation and Validation with Vue 3 and Vuelidate

Learn to build flexible, schema-driven forms in Vue 3 that dynamically generate input fields and apply validation rules using the Vuelidate library.

<!-- DynamicForm.vue -->
<template>
  <form @submit.prevent="submitForm" class="dynamic-form">
    <div v-for="field in formSchema" :key="field.model" class="form-group">
      <label :for="field.model">{{ field.label }}</label>
      <input
        v-if="field.type === 'text' || field.type === 'email' || field.type === 'password'"
        :type="field.type"
        :id="field.model"
        v-model="formData[field.model]"
        :placeholder="field.placeholder"
      />
      <textarea
        v-else-if="field.type === 'textarea'"
        :id="field.model"
        v-model="formData[field.model]"
        :placeholder="field.placeholder"
      ></textarea>
      <select
        v-else-if="field.type === 'select'"
        :id="field.model"
        v-model="formData[field.model]"
      >
        <option v-for="option in field.options" :key="option.value" :value="option.value">
          {{ option.label }}
        </option>
      </select>

      <div v-if="v$.formData[field.model].$error" class="error-message">
        <span v-for="error of v$.formData[field.model].$errors" :key="error.$uid">
          {{ error.$message }}
        </span>
      </div>
    </div>

    <button type="submit" :disabled="v$.formData.$invalid">Submit</button>
  </form>
  <pre>{{ formData }}</pre>
</template>

<script setup lang="ts">
import { reactive, computed } from 'vue'
import { required, email, minLength } from '@vuelidate/validators'
import useVuelidate from '@vuelidate/core'

// Define your form schema
interface FormField {
  model: string;
  label: string;
  type: 'text' | 'email' | 'password' | 'textarea' | 'select';
  placeholder?: string;
  options?: { label: string; value: string }[];
  validation?: any; // Vuelidate rules
}

const formSchema: FormField[] = reactive([
  {
    model: 'username',
    label: 'Username',
    type: 'text',
    placeholder: 'Enter your username',
    validation: { required, minLength: minLength(3) }
  },
  {
    model: 'email',
    label: 'Email',
    type: 'email',
    placeholder: 'Enter your email',
    validation: { required, email }
  },
  {
    model: 'password',
    label: 'Password',
    type: 'password',
    placeholder: 'Enter your password',
    validation: { required, minLength: minLength(6) }
  },
  {
    model: 'message',
    label: 'Message',
    type: 'textarea',
    placeholder: 'Your message',
    validation: { required, minLength: minLength(10) }
  },
  {
    model: 'country',
    label: 'Country',
    type: 'select',
    options: [
      { label: 'Select a country', value: '' },
      { label: 'USA', value: 'us' },
      { label: 'Canada', value: 'ca' },
      { label: 'Mexico', value: 'mx' }
    ],
    validation: { required }
  }
])

// Initialize formData based on schema
const formData = reactive<{ [key: string]: any }>({})
formSchema.forEach(field => {
  formData[field.model] = field.type === 'select' ? (field.options?.[0]?.value || '') : ''
})

// Dynamically create validation rules for Vuelidate
const rules = computed(() => {
  const validationRules: { [key: string]: any } = {}
  formSchema.forEach(field => {
    if (field.validation) {
      validationRules[field.model] = field.validation
    }
  })
  return { formData: validationRules }
})

const v$ = useVuelidate(rules, { formData })

const submitForm = async () => {
  const isFormValid = await v$.value.formData.$validate()
  if (isFormValid) {
    console.log('Form submitted successfully:', formData)
    alert('Form submitted! Check console for data.')
    // Here you would typically send formData to an API
  } else {
    console.error('Form has validation errors.')
    alert('Please correct the form errors.')
  }
}
</script>

<style scoped>
.dynamic-form {
  max-width: 500px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 8px;
  background-color: #f9f9f9;
}
.form-group {
  margin-bottom: 15px;
}
.form-group label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}
.form-group input,
.form-group textarea,
.form-group select {
  width: 100%;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-sizing: border-box;
}
.error-message {
  color: red;
  font-size: 0.9em;
  margin-top: 5px;
}
button {
  padding: 10px 20px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
button:disabled {
  background-color: #a0c3e7;
  cursor: not-allowed;
}
pre {
  background-color: #eee;
  padding: 10px;
  border-radius: 4px;
  margin-top: 20px;
  white-space: pre-wrap;
}
</style>
How it works: This snippet demonstrates how to create a highly flexible, schema-driven form in Vue 3, integrating it with the Vuelidate library for validation. The `formSchema` array defines each field's type, label, and validation rules, allowing you to generate the form's UI dynamically. The `formData` reactive object stores the user's input, and `useVuelidate` dynamically applies the validation rules based on the schema. This approach significantly reduces boilerplate for complex forms, makes forms easily configurable, and centralizes validation logic, enhancing maintainability for applications requiring many forms.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs