NotArrayField
Renderless component for managing dynamic array fields with typed items, stable keys, validation state, and array operations.
<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.
Usage
Do not use the array index as the key for managing array items:
<template>
<NotArrayField
v-slot="{ items }"
path="todos"
>
<!-- Correct -->
<NotField
v-for="item in items"
:key="item.key"
>
...
</NotField>
<!-- Avoid -->
<NotField
v-for="item, index in items"
:key="index"
>
...
</NotField>
</NotArrayField>
</template>
Always change the array through the slot's mutation methods (
append, prepend, insert, remove, swap, move), rather than mutating form.values directly with methods like .push() or .splice(). Each item's stable key is tracked separately from the array's values, and only the mutation methods keep that tracking in sync with structural changes like inserting or removing an item in the middle of the array. A direct mutation still updates the values, but can leave items with the wrong identity, which defeats the purpose of using item.key in the first place.API
Props
path
Paths<TSchema> required
Dot path of the array field. It identifies the value managed by For nested arrays use dot notation:The same path is exposed through the default slot and can be used for labels, IDs, messages, and other field-specific elements.
<NotArrayField>.<template>
<NotArrayField path="todos">
...
</NotArrayField>
</template>
<template>
<NotArrayField path="user.todos">
...
</NotArrayField>
</template>
form
NotFormAPI<TSchema>
Form instance. Overrides
By default,
You can explicitly provide a form instance when needed:An explicit
This is also required when using
<NotForm> inject.By default,
<NotArrayField> uses the form instance provided by a surrounding <NotForm>.You can explicitly provide a form instance when needed:
<template>
<NotArrayField
:form="form"
path="todos"
>
...
</NotArrayField>
</template>
form takes priority over the form provided by a <NotForm> ancestor.This is also required when using
<NotArrayField> as a standalone component outside of a <NotForm>.itemSchema
TItemSchema
The schema for an array item.
It is used for type inference only — it is never read at runtime — so that array mutation methods such as
It is used for type inference only — it is never read at runtime — so that array mutation methods such as
append, prepend, insert, and update receive the correct types for the values you pass them. It does not itself validate items; validation of array items still happens through the form's main schema, the same as any other field.Slots
default
The default slot receives the full array state and all available array operations.
<template>
<NotArrayField
v-slot="slotProps"
path="todos"
>
...
</NotArrayField>
</template>
path
string
Dot path of this array field.
Useful for labels, inputs, IDs, messages, and other field-specific elements.
Useful for labels, inputs, IDs, messages, and other field-specific elements.
<template>
<NotArrayField
v-slot="{ path }"
path="todos"
>
<NotMessage :path="path" />
</NotArrayField>
</template>
errors
Array<Issue>
Validation issues reported directly against the array field's own path.
It contains all validation issues reported for the array field itself during the most recent validation run — for example, from a
It contains all validation issues reported for the array field itself during the most recent validation run — for example, from a
.min() or .max() rule on the array as a whole.<template>
<NotArrayField
v-slot="{ errors }"
path="todos"
>
<input v-model="form.values.todos">
<ul v-if="errors.length > 0">
<li
v-for="error in errors"
:key="error.message"
>
{{ error.message }}
</li>
</ul>
</NotArrayField>
</template>
errors does not include issues from individual items — only issues attached to the array field's own path. An item that fails validation (for example, an empty required tag) makes isValidfalse, but does not add anything to this array. To show an individual item's own issues, read errors from a <NotField> or nested <NotArrayField> scoped to that item's path, as shown in the demo.isValid
boolean
Whether the array field's own path, and every path nested underneath it, have no issues.
It is
It is
true if the array field's own errors are empty and no issue exists anywhere nested inside the array — an item's own value, a field inside an object item, or an item inside a nested array of its own, at any depth. Unlike errors above, this fully recurses.<template>
<NotArrayField
v-slot="{ isValid }"
path="todos"
>
<span v-if="isValid">Valid</span>
</NotArrayField>
</template>
This recursion is genuinely unlimited depth, not just one level. For a
groups array where each group has its own nested name and tags array — the pattern from Nested Arrays below — an issue on groups.0.nameorgroups.0.tags.1 both make the outer groups field's isValidfalse, and both also make an inner <NotArrayField path="groups.0.tags">'s own isValidfalse where applicable. errors on either field, however, always stays empty for a nested issue — only isValid (and isTouched/isDirty/isValidating below) roll up.isTouched
boolean
Whether the array field's own path, or any path nested underneath it, has been touched.
Recurses to any depth, the same way as
Recurses to any depth, the same way as
isValid above.<template>
<NotArrayField
v-slot="{ isTouched, isValid }"
path="tags"
>
<p v-if="isTouched && !isValid">
Please enter valid tags.
</p>
</NotArrayField>
</template>
isDirty
boolean
Whether the array field's own path, or any path nested underneath it, differs from the baseline. Recurses to any depth, the same way as
isValid above.<template>
<NotArrayField
v-slot="{ isDirty }"
path="todos"
>
<span v-if="isDirty">Unsaved changes</span>
</NotArrayField>
</template>
isValidating
boolean
Whether the array field's own path, or any path nested underneath it, is currently validating. Recurses to any depth, the same way as
isValid above.<template>
<NotArrayField
v-slot="{ isValidating }"
path="skills"
>
<span v-if="isValidating">
Checking skills...
</span>
</NotArrayField>
</template>
items
Array<NotArrayFieldItem<TSchema>>
Contains metadata for every item in the array.
Each item has:
Each item has:
key
string
A stable identity for
v-for:key that remains the same even if the item's position changes.path
string
The dot path of this item at its current index.
index
number
The item's current position in the array.
The
key remains stable when items are reordered, while index and path change with the item's current position — as long as the array is only changed through the slot's mutation methods. See the caution above.append
(value: InferInput<TItemSchema>) => void
Appends a value to the end of the array.
<template>
<NotArrayField
v-slot="{ append }"
path="items"
>
<button
type="button"
@click="append({ name: 'New Item' })"
>
Add Item
</button>
</NotArrayField>
</template>
prepend
(value: InferInput<TItemSchema>) => void
Inserts a value at the beginning of the array, shifting every existing item's touched, dirty, and error state — at any depth underneath that item — forward by one position along with it.
<template>
<NotArrayField
v-slot="{ prepend }"
path="items"
>
<button
type="button"
@click="prepend({ name: 'New Item' })"
>
Add to Start
</button>
</NotArrayField>
</template>
insert
(index: number, value: InferInput<TItemSchema>) => void
Inserts a value at the specified index, shifting later items — and their touched, dirty, and error state, at any depth underneath them — back by one position.
<template>
<NotArrayField
v-slot="{ insert }"
path="items"
>
<button
type="button"
@click="insert(2, { name: 'Inserted Item' })"
>
Insert at index 2
</button>
</NotArrayField>
</template>
remove
(index: number) => void
Removes the item at the specified index from the array.
Its touched, dirty, and error state — at any depth underneath that item — is discarded along with it, rather than passed to whichever item ends up at that index afterward. Every later item's own state shifts back by one position to follow it to its new index.
Its touched, dirty, and error state — at any depth underneath that item — is discarded along with it, rather than passed to whichever item ends up at that index afterward. Every later item's own state shifts back by one position to follow it to its new index.
<template>
<NotArrayField
v-slot="{ remove }"
path="items"
>
<button
type="button"
@click="remove(1)"
>
Remove item at index 1
</button>
</NotArrayField>
</template>
update
(index: number, value: InferInput<TItemSchema>) => void
Replaces the item at the specified index with a new value without changing its key.
<template>
<NotArrayField
v-slot="{ update }"
path="items"
>
<button
type="button"
@click="update(1, { name: 'Updated Item' })"
>
Update item at index 1
</button>
</NotArrayField>
</template>
swap
(indexA: number, indexB: number) => void
Swaps the positions of two items in the array, moving keys and form state — at any depth underneath each item — with them.
<template>
<NotArrayField
v-slot="{ swap }"
path="items"
>
<button
type="button"
@click="swap(0, 1)"
>
Swap items at index 0 and 1
</button>
</NotArrayField>
</template>
move
(from: number, to: number) => void
Moves an item from one index to another, shifting the items in between and moving keys and form state — at any depth underneath each item — with them.
<template>
<NotArrayField
v-slot="{ move }"
path="items"
>
<button
type="button"
@click="move(0, 2)"
>
Move item from index 0 to 2
</button>
</NotArrayField>
</template>