npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xosen/vuetify-form

v0.1.0

Published

Dynamic form builder for Vuetify 3 with VineJS validation

Readme

@xosen/vuetify-form

Dynamic form builder for Vuetify 3 with VineJS validation and an extensible component registry.

Installation

pnpm add @xosen/vuetify-form

Peer dependencies: vue ^3.3.0, vuetify ^3.4.0.

Setup

import { createApp } from 'vue';
import { createFormBuilder } from '@xosen/vuetify-form';
import PhoneInput from './components/PhoneInput.vue';

const app = createApp(App);

app.use(createFormBuilder({
  locale: () => i18n.global.locale.value, // for VineJS validation messages
  components: {
    phone: PhoneInput,                    // register custom field types
  },
}));

Plugin options:

| Option | Type | Default | Description | |---|---|---|---| | locale | () => string | — | Locale getter for VineJS validation messages | | components | Record<string, Component> | {} | Custom field components to register | | registerGlobally | boolean | true | Register components in global registry |

Basic Usage

<template>
  <XFormBuilder
    v-model="formData"
    :schema="schema"
    @submit="handleSubmit"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { XFormBuilder } from '@xosen/vuetify-form'
import type { FormField } from '@xosen/vuetify-form'

const formData = ref({})

const schema: FormField[] = [
  { name: 'email', type: 'email', label: 'Email', required: true },
  { name: 'password', type: 'password', label: 'Password', required: true },
]

function handleSubmit(data: Record<string, any>) {
  console.log('Submitted:', data)
}
</script>

XFormBuilder

Props

| Prop | Type | Default | Description | |---|---|---|---| | schema | FormField[] \| FormSchema | required | Form schema definition | | modelValue | Record<string, any> | {} | Form data (v-model) | | readonly | boolean | false | Make all fields readonly | | disabled | boolean | false | Disable all fields | | variant | string | 'outlined' | Default Vuetify variant | | density | string | 'default' | Default Vuetify density | | vineSchema | any | — | VineJS validation schema |

Events

| Event | Payload | Description | |---|---|---| | update:modelValue | Record<string, any> | Form data changed | | submit | Record<string, any> | Form submitted |

Exposed Methods

const formRef = ref<InstanceType<typeof XFormBuilder>>()

// Validate the form (Vuetify rules + VineJS)
const { valid } = await formRef.value.validate()

// Reset form data and validation
formRef.value.reset()

// Clear validation errors only
formRef.value.resetValidation()

// Direct access
formRef.value.formData   // Ref<Record<string, any>>
formRef.value.isValid    // ComputedRef<boolean>

Schema Formats

Array (simple)

const schema: FormField[] = [
  { name: 'email', type: 'email', label: 'Email' },
  { name: 'password', type: 'password', label: 'Password' },
]

Object (with defaults and validation)

import type { FormSchema } from '@xosen/vuetify-form'

const schema: FormSchema = {
  cols: 6,                      // default column width for all fields
  variant: 'outlined',          // default variant
  density: 'comfortable',       // default density
  vineSchema: vine.object({     // VineJS validation
    email: vine.string().email(),
    password: vine.string().minLength(8),
  }),
  fields: [
    { name: 'email', type: 'email', label: 'Email' },
    { name: 'password', type: 'password', label: 'Password', cols: 12 },
  ],
}

Field Types

text / email / number

{
  name: 'username',
  type: 'text',           // 'text' | 'email' | 'number'
  label: 'Username',
  maxlength: 50,
  counter: true,
  clearable: true,
  prependIcon: 'mdi-account',
  appendIcon: 'mdi-check',
  autocomplete: 'username',
}

password

Password field with eye toggle for visibility.

{
  name: 'password',
  type: 'password',
  label: 'Password',
  maxlength: 128,
  clearable: true,
}

textarea

{
  name: 'bio',
  type: 'textarea',
  label: 'Bio',
  rows: 4,
  autoGrow: true,
  maxlength: 500,
  counter: true,
}

select

{
  name: 'country',
  type: 'select',
  label: 'Country',
  items: ['USA', 'Canada', 'Mexico'],          // static array
  // items: () => fetch('/api/countries').then(r => r.json()),  // async
  itemTitle: 'name',
  itemValue: 'id',
  multiple: false,
  chips: false,
  clearable: true,
}

autocomplete

Enhanced autocomplete with debounced server-side search.

{
  name: 'customer',
  type: 'autocomplete',
  label: 'Customer',
  itemTitle: 'name',
  itemValue: 'id',
  multiple: false,
  chips: false,
  clearable: true,
  // Static or async items (loaded once on mount)
  items: async () => api.getCustomers(),
  // OR: server-side search (debounced 500ms)
  onLoad: async ({ search, connector }) => {
    return connector.searchCustomers(search)
  },
  connector: apiClient,
}

checkbox

{
  name: 'agree',
  type: 'checkbox',
  label: 'I agree to the terms',
  trueValue: 'yes',
  falseValue: 'no',
}

switch

{
  name: 'active',
  type: 'switch',
  label: 'Active',
  inset: true,
  color: 'primary',
  trueValue: 1,
  falseValue: 0,
}

radio

{
  name: 'priority',
  type: 'radio',
  label: 'Priority',
  items: [
    { title: 'Low', value: 'low' },
    { title: 'Medium', value: 'medium' },
    { title: 'High', value: 'high' },
  ],
  itemTitle: 'title',
  itemValue: 'value',
  inline: true,
}

color

{
  name: 'theme',
  type: 'color',
  label: 'Theme Color',
  mode: 'hex',
  hideSliders: false,
  hideInputs: false,
  hideCanvas: false,
}

date

{
  name: 'birthday',
  type: 'date',
  label: 'Birthday',
  min: '1900-01-01',
  max: '2026-12-31',
  clearable: true,
}

mask-input

{
  name: 'phone',
  type: 'mask-input',
  label: 'Phone',
  mask: '+# (###) ###-####',
  clearable: true,
}

group-select

Grouped select/autocomplete with divider headers.

{
  name: 'department',
  type: 'group-select',
  label: 'Department',
  items: [
    { text: 'Engineering', value: 'eng', group: 'tech' },
    { text: 'Design', value: 'design', group: 'tech' },
    { text: 'Sales', value: 'sales', group: 'business' },
  ],
  itemTitle: 'text',
  itemValue: 'value',
  itemGroup: 'group',
  groupsText: { tech: 'Technology', business: 'Business' },
  searchable: true,     // uses VAutocomplete instead of VSelect
  chips: true,
  multiple: true,
}

component (custom)

Render any Vue component as a form field.

// Inline component
{
  name: 'custom',
  type: 'component',
  component: MyCustomInput,
  props: { customProp: 'value' },
}

// Reactive props (computed from form data)
{
  name: 'custom',
  type: 'component',
  component: MyCustomInput,
  props: (formData) => ({
    options: formData.category === 'premium' ? premiumOptions : standardOptions,
  }),
}

// Render function (no v-model)
{
  type: 'component',
  render: (h, formData) => h('div', `Hello ${formData.name}`),
}

Custom components should follow the v-model pattern:

<script setup lang="ts">
defineProps<{
  modelValue?: any
  label?: string
  errorMessages?: string[]
  error?: boolean
  disabled?: boolean
  readonly?: boolean
  variant?: string
  density?: string
}>()

defineEmits<{
  'update:modelValue': [value: any]
  blur: []
}>()
</script>

Common Field Properties

All field types share these base properties:

{
  label?: string,
  placeholder?: string,
  hint?: string,
  persistentHint?: boolean,
  rules?: Array<(v: any) => boolean | string>,
  disabled?: boolean | (() => boolean),    // static or reactive
  readonly?: boolean,
  required?: boolean,
  hideDetails?: boolean | 'auto',
  variant?: 'filled' | 'outlined' | 'plain' | 'underlined' | 'solo' | 'solo-inverted' | 'solo-filled',
  density?: 'default' | 'comfortable' | 'compact',
  color?: string,
  bgColor?: string,
  autofocus?: boolean,
  cols?: number | string,                  // grid column width (1-12)
  class?: string,
  visible?: boolean | ((data: any) => boolean),
}

Conditional Visibility

const schema: FormField[] = [
  { name: 'role', type: 'select', label: 'Role', items: ['admin', 'user'] },
  {
    name: 'adminKey',
    type: 'text',
    label: 'Admin Key',
    visible: (data) => data.role === 'admin',  // only shown when role is admin
  },
]

Grid Layout

const schema: FormField[] = [
  { name: 'firstName', type: 'text', label: 'First Name', cols: 6 },
  { name: 'lastName', type: 'text', label: 'Last Name', cols: 6 },
  { name: 'email', type: 'email', label: 'Email' },  // full width (12)
]

// Or set a default for all fields via FormSchema:
const schema: FormSchema = {
  cols: 6,
  fields: [
    { name: 'firstName', type: 'text', label: 'First Name' },      // 6
    { name: 'lastName', type: 'text', label: 'Last Name' },         // 6
    { name: 'email', type: 'email', label: 'Email', cols: 12 },     // override to 12
  ],
}

Async Items

Select, autocomplete, radio, and group-select fields support async item loading:

{
  name: 'country',
  type: 'select',
  label: 'Country',
  items: async () => {
    const res = await fetch('/api/countries')
    return res.json()
  },
}

Items are loaded once on mount. A loading spinner is shown automatically while the promise resolves.

For autocomplete with server-side search, use onLoad instead:

{
  name: 'customer',
  type: 'autocomplete',
  label: 'Customer',
  onLoad: async ({ search }) => {
    const res = await fetch(`/api/customers?q=${search}`)
    return res.json()
  },
}

VineJS Validation

Schema-level validation

import { vine } from '@xosen/vuetify-form'

const vineSchema = vine.object({
  email: vine.string().email(),
  password: vine.string().minLength(8),
  age: vine.number().min(18),
})

// Pass as prop
<XFormBuilder :schema="fields" :vine-schema="vineSchema" />

// Or embed in FormSchema
const schema: FormSchema = {
  vineSchema,
  fields: [
    { name: 'email', type: 'email', label: 'Email' },
    { name: 'password', type: 'password', label: 'Password' },
    { name: 'age', type: 'number', label: 'Age' },
  ],
}

Validation runs on field blur and on explicit validate() calls. Errors display inline via Vuetify's error-messages system.

Field-level rules (standalone)

import { vineFieldRules } from '@xosen/vuetify-form'

const schema = vine.object({
  email: vine.string().email(),
})

const fields: FormField[] = [
  {
    name: 'email',
    type: 'email',
    label: 'Email',
    rules: vineFieldRules(schema, 'email', () => formData.value),
  },
]

Utility functions

import {
  vineFieldRules,      // convert VineJS field to Vuetify rules
  vineSchemaToRules,   // convert entire schema to rules map
  createVineRule,      // create rules from a single VineJS field schema
  validateWithVine,    // standalone validation (returns { valid, data?, errors? })
  configureVineJS,     // configure VineJS with i18n
  jsonRule,            // custom VineJS rule for JSON strings
  vine,                // re-exported VineJS instance
} from '@xosen/vuetify-form'

Custom JSON rule

const schema = vine.object({
  config: vine.string().optional().use(jsonRule()),
})

i18n for validation messages

Built-in translations for en, ru, uk, hu. Locale is configured via the plugin:

app.use(createFormBuilder({
  locale: () => i18n.global.locale.value,
}))

Or directly:

import { configureVineJS } from '@xosen/vuetify-form'
configureVineJS(() => i18n.global.locale.value)

Custom error transformation:

const schema: FormSchema = {
  vineSchema,
  onValidationError: (error) => {
    return i18n.global.t(`validation.${error.rule}`, { field: error.field })
  },
  fields: [...],
}

Component Registry

Register custom field types at runtime.

registerFieldType (recommended)

import { registerFieldType } from '@xosen/vuetify-form'
import PhoneInput from './components/PhoneInput.vue'

registerFieldType('phone', {
  component: PhoneInput,
  needsValidation: true,
  needsBlurHandler: true,
  needsInputHandler: true,
  extractProps: ['defaultCountry', 'preferredCountries'],
})

registerFieldTypes (batch)

import { registerFieldTypes } from '@xosen/vuetify-form'

registerFieldTypes({
  phone: { component: PhoneInput, extractProps: ['defaultCountry'] },
  'rich-text': { component: RichTextEditor, needsValidation: false },
})

FieldTypeConfig

interface FieldTypeConfig {
  component: Component;
  needsValidation?: boolean;    // show error messages (default: true)
  needsBlurHandler?: boolean;   // wire @blur for validation (default: true)
  needsInputHandler?: boolean;  // wire @update:model-value (default: true)
  needsType?: boolean;          // pass :type prop (default: false)
  needsItems?: boolean;         // pass :items prop (default: false)
  needsLoading?: boolean;       // pass :loading prop (default: false)
  extractProps?: string[];      // field-specific props to extract
}

Registry helpers

import {
  getFieldComponent,        // get registered component by type
  hasFieldComponent,         // check if type is registered
  getRegisteredFieldTypes,   // list all registered type names
} from '@xosen/vuetify-form'

TypeScript

Module augmentation

Extend the type system for custom field types:

// types.d.ts
import type { BaseFormField } from '@xosen/vuetify-form'

declare module '@xosen/vuetify-form' {
  interface CustomFieldTypes {
    phone: PhoneFormField
    'rich-text': RichTextFormField
  }
}

interface PhoneFormField extends BaseFormField {
  type: 'phone'
  defaultCountry?: string
  preferredCountries?: string[]
}

interface RichTextFormField extends BaseFormField {
  type: 'rich-text'
  toolbar?: string[]
}

Type helpers

import {
  defineFormSchema,         // type-safe object schema creation
  formFieldsObjectToArray,  // convert object schema to array
  defineSwitchField,        // narrowed type helper for switch fields
  defineGroupSelectField,   // narrowed type helper for group-select
  defineComponentField,     // narrowed type helper for component fields
} from '@xosen/vuetify-form'

Components

Standalone components exported for direct use:

import {
  XFormBuilder,          // main form builder
  XPasswordField,        // password with eye toggle
  XAutocompleteField,    // enhanced autocomplete with onLoad
  XGroupSelect,          // grouped select/autocomplete
} from '@xosen/vuetify-form'

Exports

// Component
export { XFormBuilder, XPasswordField, XAutocompleteField, XGroupSelect } from '@xosen/vuetify-form'

// Plugin
export { createFormBuilder, FormBuilderComponentsSymbol } from '@xosen/vuetify-form'
export type { FormBuilderPluginOptions } from '@xosen/vuetify-form'

// Registry
export {
  registerFieldComponent, registerFieldComponents,
  getFieldComponent, hasFieldComponent,
  getRegisteredFieldTypes,
} from '@xosen/vuetify-form'

// Validation
export {
  useVineValidation, configureVineJS, createVineRule,
  jsonRule, validateWithVine, vine, vineFieldRules, vineSchemaToRules,
} from '@xosen/vuetify-form'

// Types
export type {
  FormFieldType, CoreFieldType, CustomFieldTypes, RenderFunction, BaseFormField,
  TextFormField, TextareaFormField, SelectFormField, AutocompleteFormField,
  CheckboxFormField, SwitchFormField, RadioFormField, ColorFormField,
  DateFormField, MaskInputFormField, PhoneFormField, GroupSelectFormField,
  ComponentFormField, CoreFormField, FormField, FormFieldWithName,
  FormFieldsObject, FormFieldWithoutName, FormSchema, FormProps, FormBuilderExposed,
} from '@xosen/vuetify-form'

// Helpers
export {
  defineFormSchema, formFieldsObjectToArray,
  defineSwitchField, defineGroupSelectField, defineComponentField,
} from '@xosen/vuetify-form'

// Locales
export { locales } from '@xosen/vuetify-form'

Related Packages

License

MIT