@moirei/vuetify-dynamic-form
| 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 |
@moirei/vuetify-dynamic-form
Dynamically create Vuetify 3 forms with Vue 3. Works seamlessly with and without Nuxt.
✨ 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 indicatorsv-model:dirty- Show unsaved changes warnings, enable reset buttonsv-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.valueandfields.fieldName.attrs - Validation: Built-in validation with
validcomputed property - Values: Access all form values via
valuesref - 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
- Vue 3 Required - This version requires Vue 3
- Prop Changes:
value→modelValueinputFields→inputs@input→@update:model-value
- Validation - Now uses vee-validate v4 (different API)
- 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
A simple yet highly extendable dynamic form system, inspired by the flexibility...
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 | |
| 1.3.3 | ^2.6.11 | MIT | |
| 1.3.2 | ^2.6.11 | MIT | |
| 1.3.1 | ^2.6.11 | MIT | |
| 1.3.0 | ^2.6.11 | MIT | |
| 1.2.0 | ^2.6.11 | MIT | |
| 1.1.2 | ^2.6.11 | MIT | |
| 1.1.1 | ^2.6.11 | MIT | |
| 1.1.0 | ^2.6.11 | MIT | |
| 1.0.0 | ^2.6.11 | MIT |