Skip to content

Dynamic Form ​

Generate forms from a JSON field schema. Supports nested models, masks, regex inputs, currency, date pickers, and every VeloxUI input component — all powered by VxForm under the hood.

Nested Model ​

Dot-notation paths like user.username auto-generate nested reactive objects.

Optional, max 200 characters
vue
<script setup>
const formRef = ref()
const model = reactive({
  user: { username: '', password: '' },
  description: '',
})

const fields = [
  {
    field: 'user.username',
    label: 'Username',
    placeholder: 'Enter username',
    rules: [
      { required: true, message: 'Username is required' },
      { min: 3, max: 20, message: '3-20 characters' },
      { type: 'alphanumeric', message: 'Letters and numbers only' },
    ],
  },
  {
    field: 'user.password',
    label: 'Password',
    type: 'password',
    placeholder: 'Enter password',
    rules: [{ required: true }, { min: 8, message: 'At least 8 characters' }],
  },
  {
    field: 'description',
    label: 'Description',
    type: 'textarea',
    rows: 3,
    help: 'Optional, max 200 characters',
    rules: [{ max: 200, message: 'Max 200 characters' }],
  },
]
</script>

<template>
  <VxDynamicForm ref="formRef" :fields="fields" :model="model">
    <template #actions="{ validate, reset }">
      <VxButton @click="validate()">Submit</VxButton>
      <VxButton variant="outline" @click="reset()">Reset</VxButton>
    </template>
  </VxDynamicForm>
</template>

All Field Types ​

Every supported input type in a single form: text, password, textarea, mask, regex, currency, autocomplete, select, segment, radio, checkbox, switch, slider, rating, date, date-range, date-time, time, color.

0 / 300
Only hex characters allowed
Select a role
4
–
#6366f1
vue
<script setup>
const fields = [
  // ── Text Inputs ──
  { field: 'name', label: 'Full Name', clearable: true, trim: true, rules: { required: true } },
  { field: 'email', label: 'Email', type: 'email', clearable: true, rules: [{ required: true }, { type: 'email' }] },
  { field: 'password', label: 'Password', type: 'password', rules: [{ required: true }, { min: 8 }] },
  { field: 'bio', label: 'Bio', type: 'textarea', rows: 3, maxlength: 300, showWordLimit: true },

  // ── Masked Inputs ──
  { field: 'phone', label: 'Phone', type: 'tel', mask: '+# (###) ###-####' },
  { field: 'creditCard', label: 'Credit Card', mask: '#### #### #### ####' },
  { field: 'zipCode', label: 'Zip Code', mask: '#####' },
  { field: 'ssn', label: 'SSN', mask: '###-##-####' },

  // ── Regex-Restricted Input ──
  { field: 'hexCode', label: 'Hex Code', regex: /^#?[0-9A-Fa-f]{0,6}$/ },

  // ── Currency ──
  { field: 'salary', label: 'Salary (USD)', type: 'currency', currencyOptions: { prefix: '$ ', thousands: ',' } },
  { field: 'price', label: 'Price (EUR)', type: 'currency', currencyOptions: { prefix: '€ ', decimal: ',' } },

  // ── Autocomplete ──
  { field: 'city', label: 'City', type: 'autocomplete', options: ['New York', 'LA', 'Chicago', ...] },

  // ── Select ──
  { field: 'role', label: 'Role', type: 'select', options: [...], rules: { required: true } },

  // ── Segment ──
  { field: 'plan', label: 'Plan', type: 'segment', options: [...], defaultValue: 'free' },

  // ── Radio Group ──
  { field: 'gender', label: 'Gender', type: 'radio-group', options: [...] },

  // ── Checkbox Group ──
  { field: 'skills', label: 'Skills', type: 'checkbox-group', options: [...], defaultValue: [] },

  // ── Switch ──
  { field: 'newsletter', label: 'Newsletter', type: 'switch', defaultValue: true },

  // ── Checkbox ──
  { field: 'terms', label: 'Agreement', type: 'checkbox', placeholder: 'I accept the terms' },

  // ── Slider ──
  { field: 'experience', label: 'Experience', type: 'slider', props: { min: 0, max: 20, showInput: true } },

  // ── Rating ──
  { field: 'satisfaction', label: 'Satisfaction', type: 'rating', props: { allowHalf: true, showValue: true } },

  // ── Date & Time ──
  { field: 'birthday', label: 'Birthday', type: 'date', clearable: true },
  { field: 'vacation', label: 'Vacation Range', type: 'date-range', defaultValue: [] },
  { field: 'appointment', label: 'Appointment', type: 'date-time', clearable: true },
  { field: 'wakeUp', label: 'Wake Up Time', type: 'time', clearable: true },

  // ── Color Picker ──
  { field: 'theme', label: 'Theme Color', type: 'color', defaultValue: '#6366f1', props: { showInput: true } },
]
</script>

Grid Layout ​

Use columns + per-field span for multi-column forms. Mask and currency work in grid too.

Select
vue
<script setup>
const fields = [
  { field: 'firstName', label: 'First Name', rules: { required: true } },
  { field: 'lastName', label: 'Last Name', rules: { required: true } },
  { field: 'email', label: 'Email', type: 'email' },
  { field: 'phone', label: 'Phone', type: 'tel', mask: '+# (###) ###-####' },
  { field: 'salary', label: 'Salary', type: 'currency', currencyOptions: { prefix: '$ ' } },
  { field: 'department', label: 'Department', type: 'select', options: [...] },
  { field: 'startDate', label: 'Start Date', type: 'date' },
  { field: 'level', label: 'Level', type: 'segment', options: [...] },
  { field: 'notes', label: 'Notes', type: 'textarea', rows: 3, span: 2 }, // spans 2 columns
  { field: 'active', label: 'Active', type: 'switch', defaultValue: true },
]
</script>

<template>
  <VxDynamicForm :fields="fields" :columns="2" :gutter="20">
    <template #actions><VxButton>Submit</VxButton></template>
  </VxDynamicForm>
</template>

Conditional Fields ​

Use hidden: (model) => boolean to show/hide fields reactively.

vue
<script setup>
const fields = [
  {
    field: 'accountType', label: 'Account Type', type: 'segment',
    options: [{ label: 'Personal', value: 'personal' }, { label: 'Business', value: 'business' }],
    defaultValue: 'personal',
  },
  { field: 'fullName', label: 'Full Name', hidden: (m) => m.accountType === 'business' },
  { field: 'companyName', label: 'Company', hidden: (m) => m.accountType !== 'business' },
  { field: 'taxId', label: 'Tax ID', mask: '##-#######', hidden: (m) => m.accountType !== 'business' },
  { field: 'email', label: 'Email', type: 'email' },
]
</script>

JSON from API ​

Load field schema dynamically and render the form instantly.

vue
<script setup>
const schema = ref(null)

// Fetched from API — all fields are plain JSON-serializable objects
// {
//   field: 'ticket.title', label: 'Title',
//   rules: [{ required: true, message: 'Title is required' }]
// }

async function loadSchema() {
  schema.value = await fetch('/api/form-schema').then(r => r.json())
}
</script>

<template>
  <VxButton v-if="!schema" @click="loadSchema">Load Schema</VxButton>
  <VxDynamicForm v-if="schema" :fields="schema">
    <template #actions="{ validate }">
      <VxButton @click="validate()">Submit</VxButton>
    </template>
  </VxDynamicForm>
</template>

Custom Field Slot ​

Override any field's rendering using a named slot matching the field key.

vue
<template>
  <VxDynamicForm :fields="fields">
    <!-- Override 'avatar' field with custom component -->
    <template #avatar="{ value, set }">
      <MyAvatarUploader :value="value" @change="set" />
    </template>

    <template #actions="{ validate }">
      <VxButton @click="validate()">Submit</VxButton>
    </template>
  </VxDynamicForm>
</template>

Props ​

PropTypeDefaultDescription
fieldsDynamicFormField[]requiredField schema array
modelRecord<string, any>—Reactive model (auto-created if omitted)
rulesFormRules—Form-level rules (merged with per-field)
columnsnumber0Grid columns (0 = no grid)
gutternumber16Grid gap in px
layout'vertical' | 'horizontal' | 'inline''vertical'Form layout
labelWidthstring | number—Label width for horizontal layout
labelAlign'left' | 'right''right'Label text alignment
labelSuffixstring''Suffix after labels
size'sm' | 'default' | 'lg''default'Form size
disabledbooleanfalseDisable entire form
loadingbooleanfalseLoading state
showRequiredMarkbooleantrueShow required asterisk
requiredMarkPosition'left' | 'right''left'Asterisk position
validateOnChangebooleantrueAuto-validate on change
validateOnBlurbooleantrueAuto-validate on blur
hideErrorMessagebooleanfalseHide error messages
scrollToErrorbooleanfalseScroll to first error
statusIconbooleanfalseShow status icons
showAllErrorsbooleanfalseShow all rule errors

DynamicFormField ​

PropertyTypeDescription
fieldstringField key — supports dot notation (user.name)
labelstringLabel text
typeDynamicFormFieldTypeInput type (see table below)
placeholderstringPlaceholder text
rulesFormRule | FormRule[]Validation rules
requiredbooleanForce required mark
helpstringHelp text below field
descriptionstringTooltip on label
dependenciesstring[]Re-validate when these change
defaultValueanyDefault value
options{ label, value }[]For select, radio, checkbox, segment, autocomplete, transfer
rowsnumberTextarea rows
disabledbooleanDisable this field
readonlybooleanRead-only this field
hiddenboolean | (model) => booleanConditionally hide
spannumberColumn span in grid mode
maskstringInput mask pattern (###-####, +# (###))
regexRegExpRestrict input to regex matches
clearablebooleanShow clear button
maxlengthnumberMax character length
showWordLimitbooleanShow word count
trimbooleanAuto-trim whitespace
lazybooleanUpdate on blur only
debouncenumberDebounce input (ms)
currencyOptionsobjectCurrency format { prefix, suffix, decimal, thousands, precision }
propsRecord<string, any>Pass-through to underlying component
componentComponentCustom component (when type: 'custom')

Field Types ​

TypeComponentNotes
text (default)VxInputSupports mask, regex, trim, lazy, debounce
passwordVxInput type="password"
textareaVxInput type="textarea"Supports rows, maxlength, showWordLimit
numberVxInput
emailVxInput
searchVxInput type="search"
telVxInputBest with mask for phone patterns
urlVxInput
selectVxSelectNeeds options
checkboxVxCheckboxSingle boolean; placeholder used as label
checkbox-groupVxCheckboxGroupNeeds options
radio / radio-groupVxRadioGroupNeeds options
switchVxSwitch
sliderVxSliderPass min, max, showInput via props
ratingVxRatingPass allowHalf, showValue via props
dateVxDatePicker
date-rangeVxDateRangePicker
date-timeVxDateTimePicker
timeVxTimePicker
colorVxColorPickerPass showInput, presets via props
currencyVxCurrencyInputConfigure via currencyOptions
autocompleteVxAutocompletePass string[] or option objects
segmentVxSegmentNeeds options
transferVxTransferNeeds options (as data)
uploadVxUploadConfigure via props
customYour componentSet component prop

Exposed Methods ​

MethodSignatureDescription
validate() => Promise<{ valid, errors }>Validate all fields
validateFields(fields: string[]) => Promise<{ valid, errors }>Validate specific fields
resetFields() => voidReset validation state
clearValidation(fields?: string[]) => voidClear errors
getModel() => Record<string, any>Deep copy of current model

Slots ​

SlotPropsDescription
actions{ validate, reset, model }Form actions (buttons)
[field]{ field, value, set }Override any field's rendering