# 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

<code-preview>
<demos-native-not-array-field>



</demos-native-not-array-field>

<template v-slot:code="">
<code-collapse>
<docs-demo-code file="native/not-array-field">



</docs-demo-code>
</code-collapse>
</template>
</code-preview>

<caution>

Do not use the array index as the key for managing array items:

```vue
<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>
```

</caution>

<caution>

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.

</caution>

## API

### Props

<field-group>
<field name="path" type="Paths<TSchema>" :required="true">

Dot path of the array field. It identifies the value managed by `<NotArrayField>`.

```vue
<template>
  <NotArrayField path="todos">
    ...
  </NotArrayField>
</template>
```

For nested arrays use dot notation:

```vue
<template>
  <NotArrayField path="user.todos">
    ...
  </NotArrayField>
</template>
```

The same path is exposed through the default slot and can be used for labels, IDs, messages, and other field-specific elements.

</field>

<field name="form" type="NotFormAPI<TSchema>">

Form instance. Overrides `<NotForm>` inject.

<br />

<br />



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

<br />

<br />



You can explicitly provide a form instance when needed:

```vue
<template>
  <NotArrayField
    :form="form"
    path="todos"
  >
    ...
  </NotArrayField>
</template>
```

An explicit `form` takes priority over the form provided by a `<NotForm>` ancestor.

<br />

<br />



This is also required when using `<NotArrayField>` as a standalone component outside of a `<NotForm>`.

</field>

<field name="itemSchema" type="TItemSchema">

The schema for an array item.

<br />

<br />



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.

</field>
</field-group>

### Slots

#### `default`

The default slot receives the full array state and all available array operations.

```vue
<template>
  <NotArrayField
    v-slot="slotProps"
    path="todos"
  >
    ...
  </NotArrayField>
</template>
```

<field-group>
<field name="path" type="string">

Dot path of this array field.

<br />

<br />



Useful for labels, inputs, IDs, messages, and other field-specific elements.

```vue
<template>
  <NotArrayField
    v-slot="{ path }"
    path="todos"
  >
    <NotMessage :path="path" />
  </NotArrayField>
</template>
```

</field>

<field name="errors" type="Array<Issue>">

Validation issues reported directly against the array field's own path.

<br />

<br />



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.

```vue
<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>
```

<caution>

`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 `isValid` `false`, 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.

</caution>
</field>

<field name="isValid" type="boolean">

Whether the array field's own path, and every path nested underneath it, have no issues.

<br />

<br />



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.

```vue
<template>
  <NotArrayField
    v-slot="{ isValid }"
    path="todos"
  >
    <span v-if="isValid">Valid</span>
  </NotArrayField>
</template>
```

<tip>

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](#nested-arrays) below — an issue on `groups.0.name` *or* `groups.0.tags.1` both make the outer `groups` field's `isValid` `false`, and both also make an inner `<NotArrayField path="groups.0.tags">`'s own `isValid` `false` where applicable. `errors` on either field, however, always stays empty for a nested issue — only `isValid` (and `isTouched`/`isDirty`/`isValidating` below) roll up.

</tip>
</field>

<field name="isTouched" type="boolean">

Whether the array field's own path, or any path nested underneath it, has been touched.

<br />

<br />



Recurses to any depth, the same way as `isValid` above.

```vue
<template>
  <NotArrayField
    v-slot="{ isTouched, isValid }"
    path="tags"
  >
    <p v-if="isTouched && !isValid">
      Please enter valid tags.
    </p>
  </NotArrayField>
</template>
```

</field>

<field name="isDirty" type="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.

```vue
<template>
  <NotArrayField
    v-slot="{ isDirty }"
    path="todos"
  >
    <span v-if="isDirty">Unsaved changes</span>
  </NotArrayField>
</template>
```

</field>

<field name="isValidating" type="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.

```vue
<template>
  <NotArrayField
    v-slot="{ isValidating }"
    path="skills"
  >
    <span v-if="isValidating">
      Checking skills...
    </span>
  </NotArrayField>
</template>
```

</field>

<field name="items" type="Array<NotArrayFieldItem<TSchema>>">

Contains metadata for every item in the array.

<br />

<br />



Each item has:

<field-group>
<field name="key" type="string">

A stable identity for `v-for` `:key` that remains the same even if the item's position changes.

</field>

<field name="path" type="string">

The dot path of this item at its **current** index.

</field>

<field name="index" type="number">

The item's current position in the array.

</field>
</field-group>

<note>

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.

</note>
</field>

<field name="append" type="(value: InferInput<TItemSchema>) => void">

Appends a value to the end of the array.

```vue
<template>
  <NotArrayField
    v-slot="{ append }"
    path="items"
  >
    <button
      type="button"
      @click="append({ name: 'New Item' })"
    >
      Add Item
    </button>
  </NotArrayField>
</template>
```

</field>

<field name="prepend" type="(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.

```vue
<template>
  <NotArrayField
    v-slot="{ prepend }"
    path="items"
  >
    <button
      type="button"
      @click="prepend({ name: 'New Item' })"
    >
      Add to Start
    </button>
  </NotArrayField>
</template>
```

</field>

<field name="insert" type="(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.

```vue
<template>
  <NotArrayField
    v-slot="{ insert }"
    path="items"
  >
    <button
      type="button"
      @click="insert(2, { name: 'Inserted Item' })"
    >
      Insert at index 2
    </button>
  </NotArrayField>
</template>
```

</field>

<field name="remove" type="(index: number) => void">

Removes the item at the specified index from the array.

<br />

<br />



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.

```vue
<template>
  <NotArrayField
    v-slot="{ remove }"
    path="items"
  >
    <button
      type="button"
      @click="remove(1)"
    >
      Remove item at index 1
    </button>
  </NotArrayField>
</template>
```

</field>

<field name="update" type="(index: number, value: InferInput<TItemSchema>) => void">

Replaces the item at the specified index with a new value without changing its key.

```vue
<template>
  <NotArrayField
    v-slot="{ update }"
    path="items"
  >
    <button
      type="button"
      @click="update(1, { name: 'Updated Item' })"
    >
      Update item at index 1
    </button>
  </NotArrayField>
</template>
```

</field>

<field name="swap" type="(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.

```vue
<template>
  <NotArrayField
    v-slot="{ swap }"
    path="items"
  >
    <button
      type="button"
      @click="swap(0, 1)"
    >
      Swap items at index 0 and 1
    </button>
  </NotArrayField>
</template>
```

</field>

<field name="move" type="(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.

```vue
<template>
  <NotArrayField
    v-slot="{ move }"
    path="items"
  >
    <button
      type="button"
      @click="move(0, 2)"
    >
      Move item from index 0 to 2
    </button>
  </NotArrayField>
</template>
```

</field>
</field-group>
