NotField
<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, a validation method, and event handlers that can be bound to native inputs or custom form components.
Props
| Prop | Type | Required | Description |
|---|---|---|---|
path | string | Yes | Dot-separated path to the field within the form values. |
form | NotFormInstance<any> | No | Explicit form instance to use. Takes priority over a NotForm ancestor and is required when using NotField outside a NotForm. |
validateOn | Partial<Record<ValidationTrigger, boolean>> | No | Overrides individual form-wide validation triggers for this field. |
debounce | number | No | Delays input- and change-triggered validation by the specified number of milliseconds. |
path
The field path identifies the value managed by the field.
Simple fields use a single property name:
<NotField path="email">
...
</NotField>
Nested fields use dot notation:
<NotField path="user.email">
...
</NotField>
The same path is exposed through the default slot and can be used for labels, IDs, messages, and other field-specific elements.
form
By default, NotField uses the form instance provided by a surrounding NotForm.
You can explicitly provide a form instance when needed:
<NotField :form="form" path="email" v-slot="{ events }">
<input v-bind="events" />
</NotField>
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
validateOn allows individual validation triggers to be overridden for a field.
Only the triggers you specify are overridden; all other form-wide settings remain unchanged.
<NotField
path="username"
:validate-on="{ onInput: true }"
/>
For example, if the form validates on blur, the field above will additionally validate on input.
debounce
debounce delays input- and change-triggered validation until the user stops interacting with the field for the specified number of milliseconds.
This is particularly useful for asynchronous validation where validating on every keystroke could result in excessive requests.
<NotField
path="username"
:debounce="400"
v-slot="{ events }"
>
<input
v-model="form.values.username"
v-bind="events"
/>
</NotField>
debounce.0 to disable debouncing.Slot Props
The default slot receives the complete field state and event handlers.
<NotField v-slot="field" path="email">
...
</NotField>
| Prop | Type | Description |
|---|---|---|
path | string | The dot-separated path of the field. |
value | any | The current field value as a read-only snapshot. |
errors | StandardSchemaV1.Issue[] | Validation issues from the last validation run. |
isValid | boolean | Whether the field currently has no validation errors. |
isTouched | boolean | Whether the field has been interacted with or the form has been submitted. |
isDirty | boolean | Whether the current value differs from the initial value. |
isValidating | boolean | Whether asynchronous validation is currently running for the field. |
validate | () => ReturnType<NotFormInstance<TSchema>['validateField']> | Manually triggers validation for the field. |
events | onBlur: () => void onInput: () => void onChange: () => void onFocus: () => void | Event handlers used to connect the field to NotForm. |
path
The field's path is available directly from the slot:
<NotField v-slot="{ path }" path="email">
<label :for="path">Email</label>
</NotField>
value
value contains the current value of the field.
It is intended as a read-only snapshot for displaying or inspecting the current value.
<NotField v-slot="{ value }" path="email">
<p>Current value: {{ value }}</p>
</NotField>
value directly or use it as the source for v-model.For two-way binding, bind your input to the form's values instead:
<input v-model="form.values.email" />
errors
errors contains all validation issues reported for the field during the most recent validation run.
<NotField v-slot="{ errors }" path="email">
<input v-model="form.values.email" />
<ul v-if="errors.length">
<li v-for="error in errors" :key="error.message">
{{ error.message }}
</li>
</ul>
</NotField>
StandardSchemaV1.Issue.isValid
isValid indicates whether the field currently has no validation errors.
<NotField v-slot="{ isValid }" path="email">
<span v-if="isValid">Valid</span>
</NotField>
isTouched
isTouched indicates whether the field has been interacted with or the form has been submitted.
This can be useful for displaying validation feedback only after the user has interacted with the field:
<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>
isDirty
isDirty indicates whether the field's current value differs from its initial value.
<NotField v-slot="{ isDirty }" path="email">
<span v-if="isDirty">Unsaved changes</span>
</NotField>
isValidating
isValidating is true while asynchronous validation is running for the field.
<NotField v-slot="{ isValidating }" path="username">
<span v-if="isValidating">
Checking username...
</span>
</NotField>
This is particularly useful for fields that use asynchronous validators.
validate
validate manually triggers validation for the field.
This is useful for custom inputs that manage their own interaction events or when validation needs to be triggered programmatically from the field's UI.
<NotField v-slot="{ validate }" path="email">
<button type="button" @click="validate">
Validate email
</button>
</NotField>
Events
The events slot prop contains the event handlers NotForm uses to track field interaction and trigger validation.
<NotField v-slot="{ events }" path="email">
<input v-bind="events" />
</NotField>
The event object contains:
{
onBlur: () => void,
onInput: () => void
onChange: () => void
onFocus: () => void
}
Binding all events
For native inputs, the simplest approach is to spread the handlers:
<NotField v-slot="{ events }" path="email">
<input
v-model="form.values.email"
v-bind="events"
type="email"
/>
</NotField>
NotForm can then use the configured validation triggers and track the field's interaction state.
Binding individual events
For custom components, bind the relevant handlers individually:
<NotField v-slot="{ events }" path="country">
<CustomCombobox
v-on:focusout="events.onBlur"
v-on:pick="events.onChange"
/>
</NotField>
NotField that the corresponding interaction occurred.Basic Usage
A typical field combines the slot props with a form input and NotMessage:
<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"
/>
<NotMessage :path="path" />
</NotField>
Nested Fields
NotField supports nested object paths using dot notation.
Given a schema such as:
const schema = z.object({
user: z.object({
email: z.email(),
name: z.string(),
}),
})
You can address nested fields directly:
<NotField v-slot="{ events, path }" path="user.name">
<label :for="path">Name</label>
<input
:id="path"
v-model="form.values.user.name"
v-bind="events"
/>
<NotMessage :path="path" />
</NotField>
<NotField v-slot="{ events, path }" path="user.email">
<label :for="path">Email</label>
<input
:id="path"
v-model="form.values.user.email"
v-bind="events"
/>
<NotMessage :path="path" />
</NotField>
Per-Field Validation
Use validateOn when a field needs different validation behavior from the rest of the form.
For example, a form might normally validate on blur while a username field also validates while the user types:
<NotField
path="username"
:validate-on="{ onInput: true }"
v-slot="{ events }"
>
<input
v-model="form.values.username"
v-bind="events"
/>
</NotField>
Because validateOn is partial, unspecified triggers continue to use the form-wide configuration.
Debounced Validation
For fields with expensive or asynchronous validation, combine validateOn with debounce:
<NotField
path="username"
:validate-on="{ onInput: true }"
:debounce="400"
v-slot="{ events, isValidating, errors }"
>
<input
v-model="form.values.username"
v-bind="events"
/>
<span v-if="isValidating">
Checking...
</span>
<p v-for="error in errors" :key="error.message">
{{ error.message }}
</p>
</NotField>
Validation waits until the user stops typing for 400ms before running.
This helps prevent unnecessary validation calls while the user is entering a value.
Standalone Fields
NotField can be used outside a NotForm by providing a form instance explicitly:
<NotField
:form="form"
path="email"
v-slot="{ events }"
>
<input
v-model="form.values.email"
v-bind="events"
type="email"
/>
</NotField>
form instance takes priority over any form provided through a NotForm ancestor.