GitHub

Quickstart

Quick walk through of how to use notform.

This page walks through building a complete form from scratch. It covers the four components you will use in almost every form.

How it works

Create a form instance

useNotForm takes a schema and initial values. It returns the instance you work with throughout the form.

<script setup lang="ts">
import { useNotForm } from 'notform'
import { z } from 'zod'

const form = useNotForm({
  initialValues: { email: '', password: '' },
  onSubmit: async (values) => {
    // Called only when schema passes. values is typed.
    await login(values)
  },
  schema: z.object({
    email: z.email('Enter a valid email'),
    password: z.string().min(8, 'At least 8 characters'),
  }),
})
</script>

onSubmit only runs when validation passes. If validation fails on submit, it is never called.

Wrap with <NotForm>

<NotForm> renders a <form> element and makes the instance available to all descendants through Vue's provide/inject. Pass the instance once here — you do not need to pass it to every field.

<template>
  <NotForm
    :form="form"
    @submit="form.submit"
  >
    <!-- fields and submit button -->
  </NotForm>
</template>

Bind @submit to form.submit. It calls event.preventDefault() when validation fails or when onSubmit is defined, so you never need to wire that up yourself.

Add fields with <NotField>

<NotField> is renderless. It tracks state for one field and hands it to the slot. Spread events onto any native input.

<template>
  <NotField
    v-slot="{ events }"
    path="email"
  >
    <label :for="path">
      Email
      <input
        :id="path"
        v-model="form.values.email"
        type="email"
        v-bind="events"
      >
    </label>
  </NotField>
</template>
  • path — dot-separated path to the value in form.values
  • v-model binds directly to form.values — no unwrapping
  • v-bind="events" attaches onBlur, onInput, onChange, onFocus

Show errors with <NotMessage>

<NotMessage> renders the first error for a path. It renders nothing when there is no error.

<template>
  <NotField
    v-slot="{ events, path }"
    path="email"
  >
    <label :for="path">
      Email
      <input
        :id="path"
        v-model="form.values.email"
        v-bind="events"
      >
    </label>

    <NotMessage :path="path" />
  </NotField>
</template>

Next steps