NotField

Renderless component for individual form fields with validation state and event handlers.

<NotField> is a renderless component that connects an individual field to NotForm's validation and state management.

It provides the field's current state, validation errors, and event handlers that can be bound to native inputs or custom form components.

Usage

A typical field combines the slot props with a form input and <NotMessage>.

API

Props

path
Paths<TSchema> required
Dot path of this field. It identifies the value managed by the field.

Fields on a top-level property use a single property name:
<template>
  <NotField path="email">
    ...
  </NotField>
</template>
Nested fields use dot notation:
<template>
  <NotField path="user.email">
    ...
  </NotField>
</template>
The same path is exposed through the default slot and can be used for labels, IDs, messages, and other field-specific elements.
form
NotFormAPI<TSchema>
Form instance. Overrides <NotForm> inject.

By default, <NotField> uses the form instance provided by a surrounding <NotForm>.

You can explicitly provide a form instance when needed:
<template>
  <NotField
    v-slot="{ events }"
    :form="form"
    path="email"
  >
    <input v-bind="events">
  </NotField>
</template>
An explicit form takes priority over the form provided by a <NotForm> ancestor.

This is also required when using <NotField> as a standalone field outside of a <NotForm>.
validateOn
Partial<Record<ValidationTrigger, boolean>>
Validation triggers merged over defaults: { onBlur: true, onChange: true }.

This allows individual validation triggers to be overridden for a field.

Only the triggers you specify are overridden; all other trigger settings remain unchanged.
<template>
  <NotField
    path="username"
    :validate-on="{ onInput: true }"
  />
</template>
For example, in addition to the defaults, the field above will additionally validate on input.

onMount is also a valid trigger, even though it has no corresponding entry in events below — there's no DOM event to bind for "the field just appeared," so instead it's opted into here and runs automatically once, when the field is created. This is useful for surfacing validation issues on a prefilled value without waiting for the user to interact with the field:
<template>
  <NotField
    path="promoCode"
    :validate-on="{ onMount: true }"
  />
</template>
validationMode
'eager' | 'lazy'
Controls how aggressively a field revalidates after it's been touched: lazy waits for the next blur or submit, eager also revalidates as the user types.

Defaults to eager.

In eager mode, once a field has an issue, it revalidates on every change/input event (subject to validateOn and debounce) so the error can clear as soon as the user fixes it. Until a field is invalid, change/input events are ignored — validation only starts once blur or submit produces the first issue.

In lazy mode, validation only runs on blur and submit, no matter how many issues are present. This produces less noise for fields where feedback on every keystroke isn't wanted.
validateOn and validationMode control different things: validateOn decides which interactions are allowed to trigger validation at all, while validationMode decides how often those allowed triggers actually fire once the field already has an issue. Setting validateOn: { onInput: true } with validationMode="lazy", for instance, still won't revalidate on every keystroke — lazy only revalidates on blur or submit regardless of which triggers validateOn allows.
debounce
number
Delays input- and change-triggered validation until the user stops interacting with the field for the specified number of milliseconds.

Omit or set to 0 to disable.

This is particularly useful for asynchronous validation, where validating on every keystroke could result in excessive requests.
<template>
  <NotField
    v-slot="{ events }"
    path="username"
    :debounce="400"
  >
    <input
      v-model="form.values.username"
      v-bind="events"
    >
  </NotField>
</template>
Blur and submit-triggered validation are always run immediately and are never delayed by debounce.

Slots

default

The default slot receives the complete field state and event handlers.

<template>
  <NotField
    v-slot="slotProps"
    path="email"
  >
    ...
  </NotField>
</template>
path
string
Dot path of this field.

Useful for labels, inputs, IDs, messages, and other field-specific elements.
<template>
  <NotField
    v-slot="{ path }"
    path="email"
  >
    <label :for="path">Email</label>
    <input :id="path">
  </NotField>
</template>
errors
Array<Issue>
Validation issues for this field path.

It contains all validation issues reported for the field during the most recent validation run.
<template>
  <NotField
    v-slot="{ errors }"
    path="email"
  >
    <input v-model="form.values.email">

    <ul v-if="errors.length > 0">
      <li
        v-for="error in errors"
        :key="error.message"
      >
        {{ error.message }}
      </li>
    </ul>
  </NotField>
</template>
isValid
boolean
Whether this field has no issues.

It is true if the field has no issues and false otherwise.
<template>
  <NotField
    v-slot="{ isValid }"
    path="email"
  >
    <span v-if="isValid">Valid</span>
  </NotField>
</template>
isTouched
boolean
Whether the user has interacted with this field.

This can be useful for displaying validation feedback only after the user has interacted with the field:
<template>
  <NotField
    v-slot="{ isTouched, isValid, events }"
    path="email"
  >
    <input
      v-model="form.values.email"
      v-bind="events"
    >

    <p v-if="isTouched && !isValid">
      Please enter a valid email address.
    </p>
  </NotField>
</template>
isDirty
boolean
Whether the value differs from the baseline.
<template>
  <NotField
    v-slot="{ isDirty }"
    path="email"
  >
    <span v-if="isDirty">Unsaved changes</span>
  </NotField>
</template>
isValidating
boolean
Whether this field is currently validating.
<template>
  <NotField
    v-slot="{ isValidating }"
    path="username"
  >
    <span v-if="isValidating">
      Checking username...
    </span>
  </NotField>
</template>
events
Record<ExcludeStrict<ValidationTrigger, 'onMount'>, () => void>
Handlers to bind to the input.

These handlers are used to track field interaction and trigger validation.

onMount is excluded here even though it's a valid validateOn trigger — see the note on validateOn above for why.
  • Binding all events:
For native inputs, the simplest approach is to spread the handlers:
<template>
  <NotField
    v-slot="{ events, path }"
    path="email"
  >
    <label :for="path">Email</label>

    input
    :id="path"
    v-model="form.values.email"
    v-bind="events"
    type="email"
    >
  </NotField>
</template>
  • Binding individual events:
For custom components, bind the relevant handlers individually:
<template>
  <NotField
    v-slot="{ events }"
    path="country"
  >
    <CustomCombobox
      @focusout="events.onBlur"
      @pick="events.onChange"
    />
  </NotField>
</template>
The handlers don't require the original DOM event as an argument. They simply notify <NotField> that the corresponding interaction occurred.