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

flemme-vue

v0.2.0

Published

Vue bindings for flemme

Readme

npm version Downloads

flemme-vue

Vue bindings for flemme

Table of contents

Installation

npm i -S flemme-vue

Usage

1. Define your form in a composable

For example, let's take a registration form: Using a type-first approach with TypeScript interfaces allows types to have proper name rather than displaying the type contents. A type content can be cryptic while a type name is always explicit.

import { RegistrationFormValues } from '@standard-schema/spec'
import { createForm } from 'flemme-vue'

export interface RegistrationFormValues {
  // …
}
// any standard-schema compliant lib does the trick
export const registrationFormValuesSchema = z.object() satisfies StandardSchema<RegistrationFormValues, RegistrationFormValues>

export const [useRegistrationForm, useRegistrationFormField] = createForm<RegistrationFormValues>({
  schema: registrationFormValuesSchema,
  validationTriggers: ['blur'], // pick among 'blur' | 'change' | 'focus'
  defaultInitialValues: { … },
})
import { RegistrationFormValues } from '@standard-schema/spec'
import { createForm } from 'flemme-vue'

// any standard-schema compliant lib does the trick
export const registrationFormValuesSchema = z.object(…)

export const [useRegistrationForm, useRegistrationFormField] = createForm({
  schema: registrationFormValuesSchema,
  validationTriggers: ['blur'], // pick among 'blur' | 'change' | 'focus'
  defaultInitialValues: { … },
})

2. Define the form component

To be reusable, a form always has two properties:

  1. Initial values (optional), which are provided in case of edition and omitted in case of creation
  2. a submit function, which may differ for creation and edition
<script setup lang="ts">
import { ref, toRef } from 'vue'
import { type FormValues, useRegistrationForm } from './useRegistrationForm'
import UsernameField from './UsernameField.vue'
import PasswordField from './PasswordField.vue'
import TagFields from './TagFields.vue'
import Errors from './Errors.vue'

type Props = {
  initialValues?: FormValues
  submit: (values: FormValues) => Promise<unknown>
}
const props = defineProps<Props>()

// trivial type, use your own `RemoteData`-ish from your projects, ie: a @pinia/colada mutation
type SubmitState = 'notAsked' | 'pending' | 'failure' | 'success'
const submitState = ref<SubmitState>('notAsked')

const { submit } = useRegistrationForm({
  initialValues: toRef(props, 'initialValues'),
  submit: toRef(props, 'submit'),
})
const handleSubmit = () => {
  submitState.value = 'pending'
  submit()
    .then(() => {
      submitState.value = 'success'
    })
    .catch(() => {
      submitState.value = 'failure'
    })
}
</script>
<template>
  <form class="form" @submit.prevent="handleSubmit">
    <!-- Insert fields here -->

    <button type="submit">Submit</button>
  </form>
</template>

3a. Define a field component for a top level value

ie: our registration form email field:

<script setup lang="ts">
import Errors from './Errors.vue'
import { useRegistrationFormField } from './useRegistrationForm'

const { value, focus, blur, path } = useRegistrationFormField('email')
</script>

<template>
  <div class="form-field">
    <label :for="path">Email</label>
    <input v-model="value" type="email" :id="path" :name="path" @focus="focus" @blur="blur" />
    <Errors :path="path" />
  </div>
</template>

3b. Define a field component for a nested value

ie: our registration form email field:

<script setup lang="ts">
import Errors from './Errors.vue'
import { useRegistrationFormField } from './useRegistrationForm'

const { value, focus, blur, path } = useRegistrationFormField('name.first')
</script>

<template>
  <div class="form-field">
    <label :for="path">First name</label>
    <input v-model="value" type="text" :id="path" :name="path" @focus="focus" @blur="blur" />
    <Errors :path="path" />
  </div>
</template>

4. Define a field component for an array value

Let's take the example of tags of shape type Tag = { id: string, label: string }

<script setup lang="ts">
import { useMyForm } from './useMyForm'
import TagField from './TagField.vue'

const { value } = useMyForm('tags')

function addTag() {
  // NOTE: array values are readonly, you must assign the ref value
  // You cannot use mutating methods like `.push(…)`
  value.value = value.value.concat({
    id: crypto.randomUUID(),
    label: `New Tag ${value.value.length + 1}`,
  })
}

function removeTagAt(index: number) {
  // NOTE: array values are readonly, you must assign the ref value
  // You cannot use mutating methods like `.splice(…)`
  value.value = value.value.filter((_, i) => i !== index)
}
</script>

<template>
  <div>
    <h4>Tags</h4>
    <TagField v-for="(tag, index) in value" :key="tag.id" :index="index" @remove="() => removeTagAt(index)" />
    <button type="button" @click="addTag">Add</button>
  </div>
</template>

5. Define a field component for a nested array value

Let's implement our TagLabelField:

<script setup lang="ts">
import { computed, toRef } from 'vue'
import { useRegistrationFormField } from './useRegistrationForm'
import Errors from './Errors.vue'

type Props = {
  index: number
}
const props = defineProps<Props>()
const emit = defineEmits<(e: 'remove') => void>()

const path = computed(() => `tags.${props.index}.label` as const)
const { value, blur, focus } = useRegistrationFormField(path)
</script>

<template>
  <div>
    <label>
      Tag #{{ index + 1 }}
      <input v-model="value" type="text" @focus="focus" @blur="blur" />
      <Errors :path="path" />
    </label>
    <button type="button" @click="() => emit('remove')">Remove</button>
  </div>
</template>

6. Define an error component

An error component is useful to display any error at any path:

<script setup lang="ts">
import { computed, toRef } from 'vue'
import { RegistrationFormValues, useRegistrationFormField } from './useRegistrationForm'
import { Path } from 'flemme'

type Props = {
  path: Path<RegistrationFormValues>
}
const props = defineProps<Props>()
const { errorMessage, isTouched, hasFormBeenSubmitted } = useRegistrationFormField(toRef(props, 'path'))

const shouldDisplayErrorIfAny = computed(() => isTouched.value || hasFormBeenSubmitted.value)
</script>

<template>
  <small v-if="errorMessage && shouldDisplayErrorIfAny" class="feedback-error">
    {{ errorMessage }}
  </small>
</template>

Design decisions

This library only exposes a composable API on purpose, there is no plan to support a component API.

The useForm composable (in const [useForm] = createForm(…)) does not provide any isValid state because:

  1. You can check the existence of errors for validity
  2. isValid is usually used to block submission, which is a UI anti-pattern ; forms should allow submission and report errors either via field hints or another mechanism like modals or dialogs

Limitations

  • A form can only be used once per page, no concurrent forms.
  • Dots in property names are disallowed because they clash with path handling.
  • Form values are necessarily an object or an array. If you have one value only, wrap it into a property.
  • Only object, arrays and primitives are supported. Typically Set and Map are not supported.