@moirei/vuetify-dynamic-form

Dynamically create Vuetify 3 forms with Vue 3. Works with and without Nuxt.
1,553 5
Install
npm install @moirei/vuetify-dynamic-form
Latest Version:2.1.1
Vue:3
Older releases:2
License:MIT
Last Updated:Nov 21, 2025
Links: GitHub  ·  npm
Maintainer: augustusnaz

@moirei/vuetify-dynamic-form

Dynamically create Vuetify 3 forms with Vue 3. Works seamlessly with and without Nuxt.

npm version License: MIT

✨ Features

  • Vue 3 + Vuetify 3 - Built with modern Vue 3 Composition API
  • Framework Agnostic - Works with Vue 3, Nuxt 3, Vite, or any Vue 3 setup
  • vee-validate v4 - Powerful form validation
  • Dynamic Fields - Define form fields with simple configuration
  • Auto-imports - Nuxt module with auto-imports (optional)
  • TypeScript - Full TypeScript support
  • Flexible - Customizable validation, props, and component overrides
  • Slots - Extensive slot support for customization

📦 Installation

npm install @moirei/vuetify-dynamic-form
# or
yarn add @moirei/vuetify-dynamic-form
# or
pnpm add @moirei/vuetify-dynamic-form

Peer Dependencies

Make sure you have these installed:

npm install vue@^3.0.0 vuetify@^3.0.0 vee-validate@^4.0.0

🚀 Quick Start

Vue 3 (Non-Nuxt)

import { createApp } from "vue";
import VuetifyDynamicForm from "@moirei/vuetify-dynamic-form";
import { createVuetify } from "vuetify";

const app = createApp(App);
const vuetify = createVuetify();

app.use(VuetifyDynamicForm);
app.use(vuetify);
app.mount("#app");

Nuxt 3

Add to your nuxt.config.ts:

export default defineNuxtConfig({
  modules: ["@moirei/vuetify-dynamic-form/nuxt"],
});

Components and composables will be auto-imported! 🎉

📖 Usage

Basic Example

<template>
  <VDynamicForm v-model="form" :inputs="inputs" @submit="handleSubmit" />
</template>

<script setup lang="ts">
import { ref } from "vue";

const form = ref({
  firstName: "",
  email: "",
});

const inputs = {
  firstName: {
    name: "First Name",
    type: "text",
    rules: "required|max:24",
    props: {
      filled: true,
    },
  },
  email: {
    name: "Email",
    type: "text",
    rules: "required|email",
    props: {
      filled: true,
      placeholder: "name@example.com",
    },
  },
};

const handleSubmit = (values: any) => {
  console.log("Form submitted:", values);
};
</script>

With Nuxt Auto-imports

In Nuxt 3, components are auto-imported, so you can use them directly:

<template>
  <VDynamicForm v-model="form" :inputs="inputs" />
</template>

<script setup lang="ts">
const form = ref({});
const inputs = {
  /* ... */
};
</script>

Field Configuration

const inputs = {
  // Simple text field
  firstName: {
    name: "First Name",
    type: "text",
    rules: "required|max:24",
  },

  // Select field
  country: {
    name: "Country",
    type: "select",
    rules: "required",
    props: {
      items: ["USA", "Canada", "Mexico"],
    },
  },

  // Custom component
  customField: {
    name: "Custom Field",
    component: MyCustomComponent,
    rules: "required",
  },

  // With casting
  age: {
    name: "Age",
    type: "number",
    cast: "integer",
    rules: "required|min_value:18",
  },

  // Multiple fields on same line
  firstName: {
    name: "First Name",
    type: "text",
    line: 1,
    col: { cols: 6 },
  },
  lastName: {
    name: "Last Name",
    type: "text",
    line: 1,
    col: { cols: 6 },
  },
};

Advanced Features

Custom Validation
import { defineFormInputs } from "@moirei/vuetify-dynamic-form";

const inputs = defineFormInputs({
  email: {
    name: "Email",
    type: "text",
    rules: [
      "required",
      "email",
      (value: string) => {
        if (value.endsWith("@example.com")) {
          return "Cannot use example.com email";
        }
        return true;
      },
    ],
  },
});
Slots
<template>
  <VDynamicForm v-model="form" :inputs="inputs">
    <template #firstName:field="{ input, modelValue, handleChange }">
      <VTextField
        :model-value="modelValue"
        @update:model-value="handleChange"
        v-bind="input.props"
      />
    </template>

    <template #before>
      <VCardTitle>Form Title</VCardTitle>
    </template>

    <template #after>
      <VCardActions>
        <VBtn @click="reset">Reset</VBtn>
      </VCardActions>
    </template>
  </VDynamicForm>
</template>
Form Methods
<template>
  <VDynamicForm ref="formRef" v-model="form" :inputs="inputs" />
</template>

<script setup lang="ts">
import { ref } from "vue";

const formRef = ref();

// Validate form
const validate = async () => {
  const isValid = await formRef.value?.validate();
  if (isValid) {
    // Form is valid
  }
};

// Validate specific field
const validateField = async (field: string) => {
  await formRef.value?.validateField(field);
};
</script>
Form State Tracking

Use v-model:valid, v-model:dirty, and v-model:errors to track form state:

<template>
  <VDynamicForm
    v-model="form"
    v-model:valid="isValid"
    v-model:dirty="isDirty"
    v-model:errors="formErrors"
    :inputs="inputs"
  >
    <template #after>
      <VCardActions>
        <VBtn
          :disabled="!isValid || !isDirty"
          color="primary"
          @click="handleSubmit"
        >
          Save Changes
        </VBtn>
        <VBtn :disabled="!isDirty" @click="handleReset"> Reset </VBtn>
      </VCardActions>

      <!-- Display errors -->
      <VAlert
        v-if="Object.keys(formErrors).length > 0"
        type="error"
        class="mt-4"
      >
        <div v-for="(error, field) in formErrors" :key="field">
          <strong>{{ field }}:</strong> {{ error }}
        </div>
      </VAlert>
    </template>
  </VDynamicForm>
</template>

<script setup lang="ts">
import { ref } from "vue";

const form = ref({
  firstName: "",
  email: "",
});

const isValid = ref(false);
const isDirty = ref(false);
const formErrors = ref<Record<string, string>>({});

const inputs = {
  firstName: {
    name: "First Name",
    type: "text",
    rules: "required|max:24",
  },
  email: {
    name: "Email",
    type: "text",
    rules: "required|email",
  },
};

const handleSubmit = () => {
  if (isValid.value) {
    console.log("Form submitted:", form.value);
    // Submit logic here
  }
};

const handleReset = () => {
  form.value = {
    firstName: "",
    email: "",
  };
  // Form will reset dirty state automatically
};
</script>

State Tracking Use Cases:

  • v-model:valid - Enable/disable submit buttons, show validation indicators
  • v-model:dirty - Show unsaved changes warnings, enable reset buttons
  • v-model:errors - Display error summaries, track validation issues

Using useFormInputs Composable

The useFormInputs composable provides a programmatic way to manage form inputs with type-safe field access. It's perfect for custom form layouts where you want full control over rendering.

<template>
  <VTextField
    v-model="fields.name.value"
    v-bind="fields.name.attrs"
    placeholder="Project name"
  />

  <VTextarea
    v-model="fields.description.value"
    v-bind="fields.description.attrs"
    placeholder="Add description"
  />

  <VBtn :disabled="!valid" @click="handleSubmit"> Submit </VBtn>
</template>

<script setup lang="ts">
import { useFormInputs } from "@moirei/vuetify-dynamic-form";

type ProjectInput = {
  name: string;
  description?: string;
  team: string;
};

const { fields, valid, values, setValue } = useFormInputs<ProjectInput>({
  name: "required|max:100",
  description: "max:500",
  team: "required",
});

const handleSubmit = () => {
  console.log("Form values:", values.value);
};
</script>

Key Features:

  • Type-safe: TypeScript ensures required fields are provided and optional fields are truly optional
  • Field access: Access fields via fields.fieldName.value and fields.fieldName.attrs
  • Validation: Built-in validation with valid computed property
  • Values: Access all form values via values ref
  • Set values: Use setValue(key, value) to programmatically set field values

Field Definition:

Fields can be defined as:

  • String (validation rules): "required|email"
  • Object (full field config): { rules: "required", label: "Email" }

Returned Properties:

Property Type Description
fields Record<K, Field<T[K]>> Object with field accessors (.value, .attrs)
valid ComputedRef<boolean> Form validation state
dirty ComputedRef<boolean> Form dirty state
values Ref<T> All form values
errors Ref<Record<string, string>> Field errors
errorBag Ref<ErrorBag> Detailed error information
setValue (key: K, value: T[K]) => void Set a field value
setValues (values: Partial<T>) => void Set multiple field values
validate () => Promise<boolean> Validate the form
resetForm () => void Reset form to initial state
isFieldDirty (field: string) => boolean Check if field is dirty
isFieldValid (field: string) => boolean Check if field is valid

Advanced Options:

const { fields, valid } = useFormInputs<FormType>({
  inputs: {
    email: { rules: "required|email", label: "Email Address" },
    name: "required|min:3",
  },
  initialState: { email: "user@example.com" },
  validateOnMount: true,
  validateOnBlur: true,
  validateOnChange: true,
});

📚 API Reference

VDynamicForm Props

Prop Type Default Description
modelValue Record<string, any> {} Form data (v-model)
inputs FieldInputs required Field configuration
defaults Record<string, any> {} Default values
defaultProps Record<string, any> {} Default props for all fields
loading boolean false Loading state
readonly boolean false Readonly state
disabled boolean false Disabled state
tag string 'form' Root element tag
validateOnBlur boolean true Validate on blur
validateOnChange boolean true Validate on change
validateOnInput boolean true Validate on input
validateOnModelUpdate boolean true Validate on model update
validateOnMount boolean false Validate on mount

VDynamicForm Events

Event Payload Description
update:modelValue Record<string, any> Emitted when form values change
update:valid boolean Emitted when validation state changes
update:dirty boolean Emitted when dirty state changes
update:errors Record<string, string> Emitted when errors change
submit Record<string, any> Emitted on form submit

FieldInput Type

interface FieldInput {
  key: string; // Required
  name?: string;
  label?: string;
  type?:
    | "text"
    | "select"
    | "checkbox"
    | "slider"
    | "range-slider"
    | "switch"
    | "textarea"
    | "number"
    | "radio";
  component?: string | Component;
  rules?: string | string[] | Schema | Function | Record<string, any>;
  props?: Record<string, any>;
  col?: Record<string, any>;
  line?: number | string;
  hidden?: boolean;
  disabled?: boolean;
  readonly?: boolean;
  loading?: boolean;
  hideName?: boolean;
  wheelEvents?: boolean;
  confirmEdit?: boolean;
  cast?: "string" | "number" | "boolean" | "integer";
  validateOnBlur?: boolean;
  validateOnChange?: boolean;
  validateOnInput?: boolean;
  validateOnModelUpdate?: boolean;
}

🔄 Migration from v1.x (Vue 2)

Breaking Changes

  1. Vue 3 Required - This version requires Vue 3
  2. Prop Changes:
    • value → modelValue
    • inputFields → inputs
    • @input → @update:model-value
  3. Validation - Now uses vee-validate v4 (different API)
  4. Type Changes - Field configuration structure has changed

Migration Example

Before (v1.x):

<VDynamicForm v-model="form" :input-fields="inputFields" />

After (v2.x):

<VDynamicForm v-model="form" :inputs="inputs" />

🛠️ Development

# Install dependencies
npm install

# Development
npm run dev

# Build
npm run build

# Type check
npm run type-check

# Lint
npm run lint

📝 License

MIT © Augustus Okoye

🙏 Acknowledgments

Built with:

Related Packages

jervis-form

A dynamic Vue 3 form library for building configurable forms.

2,032
@bach.software/vue-dynamic-form

A simple yet highly extendable dynamic form system, inspired by the flexibility...

1,108 0
nuxt-validate

Simple Nuxt input validation module using vee-validate

50,019 119
@mole-inc/nuxt-validate

Simple Nuxt input validation module using vee-validate

20,013 4

Version History

Version Released Vue License
2.1.1 ^3.0.0 MIT
2.1.0 ^3.0.0 MIT
2.0.0 ^3.0.0 MIT
1.3.6 ^2.0.0 MIT
1.3.4 ^2.6.14 MIT