21

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.

Props

PropTypeRequiredDescription
pathstringYesDot-separated path to the array field within the form values.
itemSchemaStandardSchemaV1NoSchema for a single array item, used for type inference.
formNotFormInstance<any>NoExplicit form instance override. Takes priority over a NotForm ancestor.
validateOnPartial<Record<'onChange' | 'onMount', boolean>>NoOverrides 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>
An explicit 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.

PropTypeDescription
pathstringThe dot-separated path of the array field.
itemsNotArrayFieldItem[]Array items with stable keys, current indexes, and field paths.
errorsStandardSchemaV1.Issue[]Validation issues for the array field from the last validation run.
isValidbooleanWhether the array field currently has no validation errors.
isTouchedbooleanWhether any item in the array has been touched.
isDirtybooleanWhether any item in the array differs from its initial value.
isValidatingbooleanWhether validation is currently running for the array field.
validate() => ReturnType<NotFormInstance<TSchema>['validateField']>Manually triggers validation for the array field.
append(value: TItem) => voidAdds an item to the end of the array.
prepend(value: TItem) => voidAdds an item to the beginning of the array.
insert(index: number, value: TItem) => voidInserts an item at a specific index.
remove(index: number) => voidRemoves an item at a specific index.
update(index: number, value: TItem) => voidReplaces the value of an item at a specific index.
swap(indexA: number, indexB: number) => voidSwaps two items.
move(from: number, to: number) => voidMoves an item from one index to another.

Array Items

The items slot prop contains metadata for every item in the array.

Each item has:

PropertyTypeDescription
keystringStable key for Vue rendering.
indexnumberCurrent index of the item.
pathstringFull 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

Tags (Min. 2)

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>
Do not use the array index as the Vue key for managed array items:
<!-- 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.