NotArrayField
<NotArrayField> is a renderless component for working with array fields in a form.
It provides the current array items, stable keys and field paths for rendering nested fields, validation state, and methods for adding, removing, updating, inserting, swapping, and moving items.
Props
| Prop | Type | Required | Description |
|---|---|---|---|
path | string | Yes | Dot-separated path to the array field within the form values. |
itemSchema | StandardSchemaV1 | No | Schema for a single array item, used for type inference. |
form | NotFormInstance<any> | No | Explicit form instance override. Takes priority over a NotForm ancestor. |
validateOn | Partial<Record<'onChange' | 'onMount', boolean>> | No | Overrides the form-wide validation triggers for the array field. |
path
path identifies the array field managed by NotArrayField.
<NotArrayField path="todos">
...
</NotArrayField>
Nested array paths are also supported:
<NotArrayField path="user.todos">
...
</NotArrayField>
The same path is exposed through the default slot.
itemSchema
itemSchema describes the schema for a single array item.
It is used for type inference so that array mutation methods such as append, prepend, insert, and update receive the correct item type.
<script setup lang="ts">
import { z } from 'zod'
const itemSchema = z.object({
done: z.boolean(),
title: z.string(),
})
</script>
<template>
<NotArrayField
v-slot="{ append }"
path="todos"
:item-schema="itemSchema"
>
<button
type="button"
@click="append({ title: '', done: false })"
>
Add Todo
</button>
</NotArrayField>
</template>
form
By default, NotArrayField uses the form instance provided by a surrounding NotForm.
You can explicitly provide a form instance:
<NotArrayField
:form="form"
path="todos"
>
...
</NotArrayField>
form takes priority over the instance provided by a NotForm ancestor.It is required when using NotArrayField outside of a NotForm.
validateOn
validateOn overrides validation behavior for the array field.
The array field supports overrides for onChange and onMount:
<NotArrayField
path="todos"
:validate-on="{ onChange: true }"
/>
Only the specified triggers are overridden. Other form-wide validation settings remain unchanged.
Slot Props
The default slot receives the full array state and all available array operations.
| Prop | Type | Description |
|---|---|---|
path | string | The dot-separated path of the array field. |
items | NotArrayFieldItem[] | Array items with stable keys, current indexes, and field paths. |
errors | StandardSchemaV1.Issue[] | Validation issues for the array field from the last validation run. |
isValid | boolean | Whether the array field currently has no validation errors. |
isTouched | boolean | Whether any item in the array has been touched. |
isDirty | boolean | Whether any item in the array differs from its initial value. |
isValidating | boolean | Whether validation is currently running for the array field. |
validate | () => ReturnType<NotFormInstance<TSchema>['validateField']> | Manually triggers validation for the array field. |
append | (value: TItem) => void | Adds an item to the end of the array. |
prepend | (value: TItem) => void | Adds an item to the beginning of the array. |
insert | (index: number, value: TItem) => void | Inserts an item at a specific index. |
remove | (index: number) => void | Removes an item at a specific index. |
update | (index: number, value: TItem) => void | Replaces the value of an item at a specific index. |
swap | (indexA: number, indexB: number) => void | Swaps two items. |
move | (from: number, to: number) => void | Moves an item from one index to another. |
Array Items
The items slot prop contains metadata for every item in the array.
Each item has:
| Property | Type | Description |
|---|---|---|
key | string | Stable key for Vue rendering. |
index | number | Current index of the item. |
path | string | Full field path to the item. |
For an array at todos, items can have paths such as:
todos[0]
todos[1]
todos[2]
The key remains stable when items are reordered, while index and path change with the item's current position.
Demo
Array Operations
Append
Add an item to the end of the array:
<NotArrayField v-slot="{ append }" path="items">
<button
type="button"
@click="append({ name: 'New Item' })"
>
Add Item
</button>
</NotArrayField>
Prepend
Add an item to the beginning of the array:
<NotArrayField v-slot="{ prepend }" path="items">
<button
type="button"
@click="prepend({ name: 'First Item' })"
>
Add to Start
</button>
</NotArrayField>
Insert
Insert an item at a specific index:
<NotArrayField v-slot="{ insert }" path="items">
<button
type="button"
@click="insert(2, { name: 'Inserted Item' })"
>
Insert at Index 2
</button>
</NotArrayField>
Remove
Remove an item at a specific index:
<NotArrayField v-slot="{ remove }" path="items">
<button
type="button"
@click="remove(1)"
>
Remove Item
</button>
</NotArrayField>
When rendering the array, use the stable key and current index from items:
<NotArrayField
v-slot="{ items, remove }"
path="items"
>
<div
v-for="item in items"
:key="item.key"
>
<span>
{{ form.values.items[item.index].name }}
</span>
<button
type="button"
@click="remove(item.index)"
>
Remove
</button>
</div>
</NotArrayField>
Update
Replace an item at a specific index:
<NotArrayField v-slot="{ update }" path="items">
<button
type="button"
@click="update(0, { name: 'Updated Item' })"
>
Update First Item
</button>
</NotArrayField>
update replaces the value at the specified index without changing the length of the array.
Swap
Swap two items:
<NotArrayField v-slot="{ swap }" path="items">
<button
type="button"
@click="swap(0, 1)"
>
Swap First Two
</button>
</NotArrayField>
Move
Move an item from one index to another:
<NotArrayField v-slot="{ move }" path="items">
<button
type="button"
@click="move(0, 5)"
>
Move First to Last
</button>
</NotArrayField>
Stable Keys
NotArrayField provides a stable key for each item.
Use it as the Vue v-for key:
<NotArrayField
v-slot="{ items }"
path="items"
>
<div
v-for="item in items"
:key="item.key"
>
...
</div>
</NotArrayField>
<!-- Avoid -->
<div
v-for="(item, index) in form.values.items"
:key="index"
>
...
</div>
The provided key remains stable when items are reordered, allowing Vue to correctly track each item.
Using Item Paths
Each item exposes a path that can be passed to nested fields.
For an item in todos:
todos[0]
A nested field can extend that path:
todos[0].title
Example:
<NotArrayField
path="todos"
v-slot="{ items }"
>
<div
v-for="item in items"
:key="item.key"
>
<NotField
v-slot="{ events }"
:path="`${item.path}.title`"
>
<input
v-model="form.values.todos[item.index].title"
v-bind="events"
/>
<NotMessage :path="`${item.path}.title`" />
</NotField>
</div>
</NotArrayField>
This keeps field paths tied to the current position of each item.
Array Validation
NotArrayField exposes validation state for the array through the slot.
<NotArrayField
path="todos"
v-slot="{
errors,
isValid,
isTouched,
isDirty,
isValidating,
}"
>
<p v-if="isValid">
Array is valid.
</p>
<p v-if="isTouched">
Array has been touched.
</p>
<p v-if="isDirty">
Array has been modified.
</p>
<p v-if="isValidating">
Validating...
</p>
<ul v-if="errors.length">
<li
v-for="(error, index) in errors"
:key="index"
>
{{ error.message }}
</li>
</ul>
</NotArrayField>
The errors slot prop contains all validation issues reported for the array field during the last validation run.
isTouched
isTouched indicates whether any item in the array has been touched.
isDirty
isDirty indicates whether any item in the array differs from its initial value.
isValidating
isValidating indicates that validation is currently running for the array field.
isValid
isValid indicates whether the array currently has no validation errors.
Manual Validation
Use validate to manually trigger validation for the array:
<NotArrayField
path="todos"
v-slot="{ validate }"
>
<button
type="button"
@click="validate"
>
Validate Todos
</button>
</NotArrayField>
This is useful when array changes are performed programmatically and validation needs to be triggered explicitly.
Nested Arrays
NotArrayField can be nested to manage arrays inside array items.
For example:
const numberArraySchema = z.array(z.number())
const schema = z.object({
matrix: z.array(numberArraySchema),
})
The outer array can provide the path for each row:
<NotArrayField
path="matrix"
v-slot="{ items: rows }"
>
<div
v-for="row in rows"
:key="row.key"
>
<NotArrayField
:path="row.path"
v-slot="{ items: cells }"
>
<div
v-for="cell in cells"
:key="cell.key"
>
<NotField
v-slot="{ events }"
:path="cell.path"
>
<input
v-model="form.values.matrix[row.index][cell.index]"
v-bind="events"
type="number"
/>
</NotField>
</div>
</NotArrayField>
</div>
</NotArrayField>
Each nested NotArrayField receives its parent's item path, allowing the same API to be composed for deeper array structures.