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

@conexusnuclear/lvform

v0.1.2

Published

Modern Vue 3 form library for Laravel backends - drop-in replacement for vform

Readme

lvform

Modern Vue 3 form library for Laravel backends - drop-in replacement for vform

lvform is a lightweight library designed specifically for Vue 3 applications to handle forms and validation when using Laravel as a back-end. It provides proper Vue 3 reactivity, Composition API support, and zero console warnings.

Why lvform?

If you're using vform with Vue 3, you may have noticed console warnings related to:

  • ❌ Reactivity issues when using Composition API
  • ❌ Element-Plus and other UI library integration warnings
  • ❌ Prop mutation warnings with vform components
  • ❌ Type check failures with modern Vue 3 components

lvform solves all these issues by being built from the ground up for Vue 3:

  • Zero console warnings - Clean reactive implementation
  • Composition API first - Built with reactive() and toRefs()
  • Options API compatible - Works with traditional Vue syntax
  • TypeScript native - Full type safety with generics
  • Modern components - Designed for <script setup>
  • Drop-in replacement - Compatible with vform API

Installation

npm install @conexusnuclear/lvform

The package has peer dependencies that you'll also need:

npm install @conexusnuclear/lvform axios vue@^3.0.0

Or add to your package.json:

{
  "dependencies": {
    "@conexusnuclear/lvform": "^0.1.0",
    "axios": "^1.7.0",
    "vue": "^3.0.0"
  }
}

Quick Start

Options API (Traditional Vue)

<template>
  <form @submit.prevent="login" @keydown="form.onKeydown($event)">
    <input v-model="form.username" type="text" name="username" placeholder="Username">
    <div v-if="form.errors.has('username')" v-html="form.errors.get('username')" />

    <input v-model="form.password" type="password" name="password" placeholder="Password">
    <div v-if="form.errors.has('password')" v-html="form.errors.get('password')" />

    <button type="submit" :disabled="form.busy">
      Log In
    </button>
  </form>
</template>

<script>
import { Form } from '@conexusnuclear/lvform'

export default {
  data() {
    return {
      form: new Form({
        username: '',
        password: ''
      })
    }
  },

  methods: {
    async login() {
      const response = await this.form.post('/api/login')
      // Handle successful login
      console.log(response.data)
    }
  }
}
</script>

Composition API (Modern Vue 3)

<template>
  <form @submit.prevent="login">
    <input v-model="username" type="text" placeholder="Username">
    <div v-if="errors.has('username')" v-text="errors.get('username')" />

    <input v-model="password" type="password" placeholder="Password">
    <div v-if="errors.has('password')" v-text="errors.get('password')" />

    <button type="submit" :disabled="busy">
      Log In
    </button>
  </form>
</template>

<script setup>
import { useForm } from '@conexusnuclear/lvform'

const { username, password, busy, errors, post } = useForm({
  username: '',
  password: ''
})

async function login() {
  const response = await post('/api/login')
  // Handle successful login
  console.log(response.data)
}
</script>

Laravel Controller

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class LoginController extends Controller
{
    public function login(Request $request)
    {
        $this->validate($request, [
            'username' => 'required|string',
            'password' => 'required|string|min:6',
        ]);

        // Your authentication logic here
        // ...

        return response()->json([
            'message' => 'Login successful',
            'user' => auth()->user()
        ]);
    }
}

API Reference

Form Class

Creating an Instance

import { Form } from '@conexusnuclear/lvform'

const form = new Form({
  email: '',
  name: '',
  age: null
})

// Or with Form.make()
const form = Form.make({
  email: '',
  name: ''
})

Instance Properties

/**
 * Indicates if the form is being submitted to the server.
 */
form.busy: boolean

/**
 * Indicates if the last submission was successful.
 */
form.successful: boolean

/**
 * Indicates if the form was recently successful (for 2 seconds).
 */
form.recentlySuccessful: boolean

/**
 * The validation errors from the server.
 */
form.errors: Errors

/**
 * The upload progress information.
 */
form.progress: { total: number, loaded: number, percentage: number } | undefined

/**
 * Computed property indicating if there are any validation errors.
 */
form.hasErrors: boolean

/**
 * Access to form data
 */
form.data: T  // Your form data type

Instance Methods

/**
 * Submit the form via HTTP request
 */
form.submit(method: string, url: string, config?: AxiosRequestConfig)
form.post(url: string, config?: AxiosRequestConfig)
form.put(url: string, config?: AxiosRequestConfig)
form.patch(url: string, config?: AxiosRequestConfig)
form.delete(url: string, config?: AxiosRequestConfig)
form.get(url: string, config?: AxiosRequestConfig)

/**
 * Clear all form errors
 */
form.clear()

/**
 * Reset form data to original values
 */
form.reset()

/**
 * Update form data (partial update)
 */
form.update(data: Partial<T>)

/**
 * Fill form data (complete replacement)
 */
form.fill(data: T)

/**
 * Clear error for a field on keydown
 */
form.onKeydown(event: KeyboardEvent)

Setting a Custom Axios Instance

import axios from 'axios'
import { Form } from '@conexusnuclear/lvform'

const instance = axios.create({
  baseURL: 'https://api.example.com',
  headers: {
    'Authorization': `Bearer ${token}`
  }
})

Form.axios = instance

Errors Class

const form = new Form({})
const errors = form.errors

Instance Methods

/**
 * Get all errors as an object
 */
errors.all(): Record<string, string[]>

/**
 * Check if a field has an error
 */
errors.has(field: string): boolean

/**
 * Check if any of the given fields have errors
 */
errors.hasAny(...fields: string[]): boolean

/**
 * Check if there are any errors
 */
errors.any(): boolean

/**
 * Get the first error message for a field
 */
errors.get(field: string): string | undefined

/**
 * Get all error messages for a field
 */
errors.getAll(field: string): string[]

/**
 * Get errors only for specific fields
 */
errors.only(...fields: string[]): string[]

/**
 * Get all errors in a flat array
 */
errors.flatten(): string[]

/**
 * Clear error(s) - clears all if no field specified
 */
errors.clear(field?: string)

/**
 * Set errors from server response
 */
errors.set(errors: Record<string, string | string[]>)

/**
 * Set a specific field error
 */
errors.set(field: string, message: string)

useForm Composable

The useForm composable provides a modern Composition API interface:

<script setup>
import { useForm } from '@conexusnuclear/lvform'

const form = useForm({
  email: '',
  name: '',
  age: null
})

// Destructure for convenience
const { email, name, age, busy, errors, successful, post, reset } = form

async function submit() {
  const response = await post('/api/users')
  // Handle response
}
</script>

<template>
  <form @submit.prevent="submit">
    <!-- Direct v-model binding with .value -->
    <input v-model="email.value" type="email">

    <!-- Or use the form object -->
    <input v-model="form.email.value" type="text">

    <!-- Access errors -->
    <div v-if="errors.has('email')">{{ errors.get('email') }}</div>

    <!-- Check busy state -->
    <button :disabled="busy.value">Submit</button>
  </form>
</template>

Important: When using the destructured properties, remember to use .value for reactive values:

const { email, busy } = useForm({ email: '' })

// Correct
console.log(email.value)
if (busy.value) { }

// Incorrect
console.log(email)  // This is a Ref object, not the value

Advanced Usage

File Uploads

<template>
  <form @submit.prevent="submit" @keydown="form.onKeydown($event)">
    <input v-model="form.name" type="text" name="name">
    <div v-if="form.errors.has('name')" v-text="form.errors.get('name')" />

    <input type="file" name="file" @change="handleFile">
    <div v-if="form.errors.has('file')" v-text="form.errors.get('file')" />

    <div v-if="form.progress">
      Progress: {{ form.progress.percentage }}%
    </div>

    <button type="submit" :disabled="form.busy">Upload</button>
  </form>
</template>

<script setup>
import { Form } from '@conexusnuclear/lvform'
import { ref } from 'vue'

const form = ref(new Form({
  name: '',
  file: null
}))

function handleFile(event) {
  const file = event.target.files[0]
  form.value.file = file
}

async function submit() {
  const response = await form.value.post('/api/upload')
  // File uploaded successfully
}
</script>

With Element-Plus

<template>
  <el-form @submit.prevent="submit">
    <el-form-item label="Email">
      <el-input v-model="email.value" type="email" />
      <div v-if="errors.has('email')" class="error">
        {{ errors.get('email') }}
      </div>
    </el-form-item>

    <el-form-item label="Date">
      <el-date-picker v-model="eventDate.value" />
      <div v-if="errors.has('event_date')" class="error">
        {{ errors.get('event_date') }}
      </div>
    </el-form-item>

    <el-form-item>
      <el-button type="primary" native-type="submit" :loading="busy.value">
        Submit
      </el-button>
    </el-form-item>
  </el-form>
</template>

<script setup>
import { useForm } from '@conexusnuclear/lvform'

const { email, eventDate, busy, errors, post } = useForm({
  email: '',
  eventDate: new Date()
})

async function submit() {
  await post('/api/events')
}
</script>

TypeScript Support

lvform is written in TypeScript and provides full type safety:

interface UserForm {
  email: string
  name: string
  age: number | null
  avatar: File | null
}

const form = new Form<UserForm>({
  email: '',
  name: '',
  age: null,
  avatar: null
})

// TypeScript knows the shape of form.data
form.data.email // string
form.data.age   // number | null

// With useForm
const { email, name } = useForm<UserForm>({
  email: '',
  name: '',
  age: null,
  avatar: null
})

email.value = '[email protected]'  // ✅ Type-safe
email.value = 123  // ❌ TypeScript error

Error Handling

<script setup>
import { useForm } from '@conexusnuclear/lvform'

const form = useForm({
  email: '',
  password: ''
})

async function login() {
  try {
    const response = await form.post('/api/login')
    // Success - redirect or show message
    router.push('/dashboard')
  } catch (error) {
    // Laravel validation errors are automatically set on form.errors
    if (error.response?.status === 422) {
      // Validation failed - errors are already available
      console.log(form.errors.all())
    } else {
      // Other errors (500, network, etc.)
      console.error('Login failed:', error)
    }
  }
}
</script>

Multiple File Upload

const form = new Form({
  title: '',
  files: [] as File[]
})

function handleFiles(event: Event) {
  const target = event.target as HTMLInputElement
  if (target.files) {
    form.files = Array.from(target.files)
  }
}

await form.post('/api/documents')

Custom Axios Configuration

const form = new Form({ email: '' })

// Per-request configuration
await form.post('/api/users', {
  headers: {
    'X-Custom-Header': 'value'
  },
  timeout: 5000
})

// Upload progress tracking
await form.post('/api/upload', {
  onUploadProgress: (progressEvent) => {
    console.log('Upload progress:', form.progress)
  }
})

Migration from vform

If you're migrating from vform to lvform, the process is straightforward:

1. Update imports

- import Form from '@conexusnuclear/lvform'
+ import { Form } from '@conexusnuclear/lvform'

- import { useForm } from '@conexusnuclear/lvform'  // if vform had this
+ import { useForm } from '@conexusnuclear/lvform'

2. Update component imports (if using)

- import { Button, HasError } from 'vform/src/components/bootstrap5'
+ import { Button, HasError } from 'lvform/components/bootstrap5'

3. Composition API changes (if applicable)

With vform, you might have wrapped the Form in reactive():

// ❌ Old vform pattern (causes warnings)
const form = reactive(new Form({ ... }))

With lvform, use the Form directly or use the useForm composable:

// ✅ Option 1: Use Form directly in a ref
const form = ref(new Form({ ... }))

// ✅ Option 2: Use useForm composable (recommended)
const form = useForm({ ... })

Breaking Changes

There are minimal breaking changes:

  1. Named export: Form is now a named export instead of default

    - import Form from 'lvform'
    + import { Form } from '@conexusnuclear/lvform'
  2. useForm returns refs: In Composition API, form fields are returned as refs

    const { email } = useForm({ email: '' })
    console.log(email.value)  // Use .value to access

Everything else remains API-compatible with vform!

Browser Support

  • Modern browsers (Chrome, Firefox, Safari, Edge)
  • Vue 3.0+
  • No IE11 support (use vform if you need Vue 2/IE11)

Development

# Install dependencies
npm install

# Run tests
npm test

# Build the library
npm run build

# Type check
npm run type-check

License

MIT

Credits

lvform is inspired by and maintains API compatibility with vform by @cretueusebiu.

Built specifically for Vue 3 with modern reactive patterns and TypeScript support.