Skip to main content
TanStack Form supports arrays as form values, enabling you to build dynamic forms with lists of items.

Basic Array Usage

To work with arrays, use field.state.value on an array field with Svelte’s each blocks.

Simple Array Example

<script>
  import { createForm } from '@tanstack/svelte-form'

  const form = createForm(() => ({
    defaultValues: {
      people: [],
    },
  }))
</script>

<form.Field name="people" mode="array">
  {#snippet children(field)}
    {#each field.state.value as person, i}
      <!-- Render each person -->
    {/each}
  {/snippet}
</form.Field>
Note the mode="array" prop, which tells the field to operate in array mode.

Adding Items to Arrays

Use the pushValue method to add new items to an array field:
<form.Field name="people" mode="array">
  {#snippet children(field)}
    <button onclick={() => field.pushValue({ name: '', age: 0 })} type="button">
      Add person
    </button>
  {/snippet}
</form.Field>
The list automatically regenerates every time you call pushValue.

Accessing Nested Fields

Access individual array items using bracket notation in the field name:
<form.Field name={`people[${i}].name`}>
  {#snippet children(subField)}
    <input
      value={subField.state.value}
      oninput={(e) => {
        subField.handleChange(e.currentTarget.value)
      }}
    />
  {/snippet}
</form.Field>

Complete Array Example

Here’s a full example with adding items and nested field access:
<script lang="ts">
  import { createForm } from '@tanstack/svelte-form'

  const form = createForm(() => ({
    defaultValues: {
      people: [] as Array<{ age: number; name: string }>,
    },
    onSubmit: ({ value }) => alert(JSON.stringify(value)),
  }))
</script>

<form
  id="form"
  onsubmit={(e) => {
    e.preventDefault()
    e.stopPropagation()
    form.handleSubmit()
  }}
>
  <form.Field name="people">
    {#snippet children(field)}
      <div>
        {#each field.state.value as person, i}
          <form.Field name={`people[${i}].name`}>
            {#snippet children(subField)}
              <div>
                <label>
                  <div>Name for person {i}</div>
                  <input
                    value={person.name}
                    oninput={(e: Event) => {
                      const target = e.target as HTMLInputElement
                      subField.handleChange(target.value)
                    }}
                  />
                </label>
              </div>
            {/snippet}
          </form.Field>
        {/each}

        <button
          onclick={() => field.pushValue({ name: '', age: 0 })}
          type="button"
        >
          Add person
        </button>
      </div>
    {/snippet}
  </form.Field>

  <button type="submit"> Submit </button>
</form>

Array Field Methods

The field API provides several methods for managing arrays:

pushValue(item)

Add an item to the end of the array:
field.pushValue({ name: '', age: 0 })

removeValue(index)

Remove an item at a specific index:
field.removeValue(2) // Remove the third item

insertValue(index, item)

Insert an item at a specific position:
field.insertValue(1, { name: 'John', age: 30 })

replaceValue(index, item)

Replace an item at a specific index:
field.replaceValue(0, { name: 'Jane', age: 25 })

swapValues(indexA, indexB)

Swap two items in the array:
field.swapValues(0, 2) // Swap first and third items

moveValue(from, to)

Move an item from one index to another:
field.moveValue(0, 3) // Move first item to fourth position

clearValues()

Remove all items from the array:
field.clearValues()

Array with Remove Buttons

Add remove buttons to let users delete individual items:
<form.Field name="people">
  {#snippet children(field)}
    <div>
      {#each field.state.value as person, i}
        <div>
          <form.Field name={`people[${i}].name`}>
            {#snippet children(subField)}
              <input
                value={person.name}
                oninput={(e: Event) => {
                  const target = e.target as HTMLInputElement
                  subField.handleChange(target.value)
                }}
              />
            {/snippet}
          </form.Field>
          <button
            type="button"
            onclick={() => field.removeValue(i)}
          >
            Remove
          </button>
        </div>
      {/each}
    </div>
  {/snippet}
</form.Field>

Validating Array Fields

You can validate both the array itself and individual array items:

Array-Level Validation

Validate the entire array:
<form.Field
  name="people"
  validators={{
    onChange: ({ value }) =>
      value.length < 1 ? 'At least one person is required' : undefined,
  }}
>
  {#snippet children(field)}
    <!-- ... -->
    {#if field.state.meta.errors}
      <em>{field.state.meta.errors.join(', ')}</em>
    {/if}
  {/snippet}
</form.Field>

Item-Level Validation

Validate individual array items:
<form.Field
  name={`people[${i}].name`}
  validators={{
    onChange: ({ value }) =>
      value.length < 2 ? 'Name must be at least 2 characters' : undefined,
  }}
>
  {#snippet children(field)}
    <input
      value={field.state.value}
      oninput={(e: Event) => {
        const target = e.target as HTMLInputElement
        field.handleChange(target.value)
      }}
    />
    {#if field.state.meta.errors}
      <em>{field.state.meta.errors[0]}</em>
    {/if}
  {/snippet}
</form.Field>

Complex Nested Objects

Arrays can contain complex objects with multiple fields:
<script lang="ts">
  interface Person {
    firstName: string
    lastName: string
    age: number
    email: string
  }

  const form = createForm(() => ({
    defaultValues: {
      people: [] as Array<Person>,
    },
  }))
</script>

<form.Field name="people">
  {#snippet children(field)}
    {#each field.state.value as person, i}
      <div>
        <form.Field name={`people[${i}].firstName`}>
          {#snippet children(subField)}
            <input value={subField.state.value} oninput={(e) => subField.handleChange(e.target.value)} />
          {/snippet}
        </form.Field>

        <form.Field name={`people[${i}].lastName`}>
          {#snippet children(subField)}
            <input value={subField.state.value} oninput={(e) => subField.handleChange(e.target.value)} />
          {/snippet}
        </form.Field>

        <form.Field name={`people[${i}].age`}>
          {#snippet children(subField)}
            <input type="number" value={subField.state.value} oninput={(e) => subField.handleChange(e.target.valueAsNumber)} />
          {/snippet}
        </form.Field>

        <form.Field name={`people[${i}].email`}>
          {#snippet children(subField)}
            <input type="email" value={subField.state.value} oninput={(e) => subField.handleChange(e.target.value)} />
          {/snippet}
        </form.Field>
      </div>
    {/each}
  {/snippet}
</form.Field>

Empty State

Handle the case when the array is empty using Svelte’s else block:
<form.Field name="people">
  {#snippet children(field)}
    <div>
      {#each field.state.value as person, i}
        <!-- Render person -->
      {:else}
        <p>No people added yet. Click "Add person" to get started.</p>
      {/each}

      <button onclick={() => field.pushValue({ name: '', age: 0 })} type="button">
        Add person
      </button>
    </div>
  {/snippet}
</form.Field>

Array Keying

For better performance with reordering, use Svelte’s keyed each blocks:
<script lang="ts">
  interface Person {
    id: string
    name: string
  }

  const form = createForm(() => ({
    defaultValues: {
      people: [] as Array<Person>,
    },
  }))
</script>

<form.Field name="people">
  {#snippet children(field)}
    {#each field.state.value as person, i (person.id)}
      <form.Field name={`people[${i}].name`}>
        {#snippet children(subField)}
          <input value={subField.state.value} oninput={(e) => subField.handleChange(e.target.value)} />
        {/snippet}
      </form.Field>
    {/each}
  {/snippet}
</form.Field>

Next Steps

Build docs developers (and LLMs) love