Get Started
Introduction
Components
Accordion
Badge
Button
Card
Checkbox
Command
Dialog
Dropdown Menu
Input
Select
Switch
Table
Tabs
Toast
Tooltip
Forms
Introduction
Validation
Field Arrays

Field Arrays

Dynamic list of inputs. The array is a raw signal; the per-item rule is the same Zod schema you'd hand to createForm.

See interactive examples below.

1import { createSignal } from '@barefootjs/client'2import { z } from 'zod'34// Same per-item schema you'd nest inside createForm5const emailSchema = z6  .string()7  .min(1, 'Email is required')8  .email('Invalid email format')910const validateEmail = (v: string) => {11  const r = emailSchema.safeParse(v)12  return r.success ? '' : r.error.issues[0]?.message ?? ''13}1415type Item = { value: string; touched: boolean }1617const [items, setItems] = createSignal<Item[]>([{ value: '', touched: false }])1819const itemError = (item: Item) =>20  item.touched ? validateEmail(item.value) : ''2122const update = (i: number, value: string) =>23  setItems(items().map((it, idx) => idx === i ? { ...it, value } : it))2425const blur = (i: number) =>26  setItems(items().map((it, idx) => idx === i ? { ...it, touched: true } : it))2728const add = () =>29  setItems([...items(), { value: '', touched: false }])3031const remove = (i: number) => {32  if (items().length > 1) setItems(items().filter((_, idx) => idx !== i))33}3435{items().map((item, i) => (36  <div key={i}>37    <input38      value={item.value}39      onInput={(e) => update(i, e.target.value)}40      onBlur={() => blur(i)}41    />42    <p>{itemError(item)}</p>43    <button onClick={() => remove(i)}>X</button>44  </div>45))}46<button onClick={add}>+ Add Email</button>

#Overview

createForm targets fixed-shape records: it validates the array on submit but routes errors to dot-paths (emails.0, emails.1, …), not the top-level field, so per-item live feedback isn't reachable through field('emails').error(). Instead, store the array in a createSignal of { value, touched } objects and reuse the same per-item Zod schema you'd otherwise nest in createForm.

#Examples

#Basic Field Array

1 email(s) added

1import { createSignal } from '@barefootjs/client'2import { z } from 'zod'34// Same per-item schema you'd nest inside createForm5const emailSchema = z6  .string()7  .min(1, 'Email is required')8  .email('Invalid email format')910const validateEmail = (v: string) => {11  const r = emailSchema.safeParse(v)12  return r.success ? '' : r.error.issues[0]?.message ?? ''13}1415type Item = { value: string; touched: boolean }1617const [items, setItems] = createSignal<Item[]>([{ value: '', touched: false }])1819const itemError = (item: Item) =>20  item.touched ? validateEmail(item.value) : ''2122const update = (i: number, value: string) =>23  setItems(items().map((it, idx) => idx === i ? { ...it, value } : it))2425const blur = (i: number) =>26  setItems(items().map((it, idx) => idx === i ? { ...it, touched: true } : it))2728const add = () =>29  setItems([...items(), { value: '', touched: false }])3031const remove = (i: number) => {32  if (items().length > 1) setItems(items().filter((_, idx) => idx !== i))33}3435{items().map((item, i) => (36  <div key={i}>37    <input38      value={item.value}39      onInput={(e) => update(i, e.target.value)}40      onBlur={() => blur(i)}41    />42    <p>{itemError(item)}</p>43    <button onClick={() => remove(i)}>X</button>44  </div>45))}46<button onClick={add}>+ Add Email</button>

#Duplicate Detection

1// Reuse the per-item schema, then layer a cross-item rule on top.2const itemError = (item: Item, i: number) => {3  if (!item.touched) return ''4  const basic = validateEmail(item.value)5  if (basic) return basic6  const lower = item.value.toLowerCase()7  const isDup = items().some((o, idx) => idx !== i && o.value.toLowerCase() === lower)8  return isDup ? 'Duplicate email' : ''9}1011const duplicateCount = createMemo(() => {12  const values = items().map(it => it.value.toLowerCase().trim()).filter(v => v !== '')13  return values.length - new Set(values).size14})

#Min / Max Field Constraints

1 / 5 emails

1const MIN_FIELDS = 12const MAX_FIELDS = 534const canAdd = createMemo(() => items().length < MAX_FIELDS)5const canRemove = createMemo(() => items().length > MIN_FIELDS)67<button onClick={add} disabled={!canAdd()}>+ Add Email</button>8<p>{items().length} / {MAX_FIELDS} emails</p>