Skip to main content

Array reorder

Mark an array as reorderable with one config flag. The library wires React Hook Form's move(), ships a keyboard-operable default handle, and fires an event you can mirror back to your upstream state.

Quickstart

import { z } from 'zod';
import { ZodForm } from '@zod-to-form/react';

const schema = z.object({
members: z.array(z.string())
});

const componentConfig = {
fields: {
members: {
arrayConfig: {
reorder: true,
addLabel: '+ Add member',
onReorder: (from, to) => {
console.log(`Reordered: ${from}${to}`);
}
}
}
}
};

<ZodForm schema={schema} componentConfig={componentConfig} />;

What renders

For each row the library mounts a componentMap.ArrayReorderHandle component. The default is a vertical button group:

┌──────────────┬───┬──────────┐
│ <input> │ ↑ │ Remove │
│ │ ↓ │ │
└──────────────┴───┴──────────┘

is disabled on the first row, on the last. Both are keyboard-operable by default.

Override the handle

Replace ArrayReorderHandle in your component map to use any gesture library:

import { defaultComponentMap } from '@zod-to-form/react';
import { useDrag, useDrop } from 'react-dnd';

function DragHandle({ index, total, onMove, disabled }) {
// your react-dnd, dnd-kit, or framer-motion implementation here
}

const components = {
...defaultComponentMap,
ArrayReorderHandle: DragHandle
};

<ZodForm schema={schema} components={components} />;

The library guarantees:

  • index is the row's current index.
  • total is the count of form-driven rows (excludes ghost rows).
  • onMove(from, to) calls RHF's move() and then your onReorder.
  • disabled is true when the field is disabled or readOnly. Reorder is not tied to min/max length — fixed-length arrays remain reorderable. The default handle additionally disables the "move up" button on the first row and the "move down" button on the last row.

Accessibility

The default handle includes:

  • role="group" on the wrapper with an aria-label referencing the row.
  • Per-button aria-labels ("Move row 2 up", "Move row 2 down").
  • disabled attributes at array boundaries.
  • Full keyboard operability via Tab and Space/Enter.

If you replace the default with a drag-based component, preserve the keyboard fallback path. Drag-only reorder excludes keyboard users and screen-reader users.

Reorder + ghost rows

Ghost rows (arrayConfig.before / arrayConfig.after) do not participate in reorder. The handle is per-row on form-driven rows only; reordering shifts indices among real rows while ghost positions stay fixed. See Ghost rows.

Common pitfalls

  • Forgetting arrayConfig.reorder is false by default: existing forms see no change after a library upgrade. You must opt in per array.
  • Not supplying onReorder: the form state updates correctly even without it, but if you hold a parallel copy upstream (graph store, server cache) you'll fall out of sync. Add the callback the moment reorder ships in production.
  • Reorder on fixed-length arrays: it works. Length constraints block add/remove only; reorder works because total length is unchanged.