FieldArray
Grouping, repeating and numbering fields across a long form.
@luc/ui/formsExamples
A section, and repeating rows
`FormSection` groups fields under a heading and a description; the fields inside keep their own `Field` wiring.
Identity
What this run is called.
<FormSection title="Identity" description="What this run is called.">
<Input defaultValue="Momentum v3" aria-label="Run name" />
</FormSection>Add, remove and reorder rows
The array lives in the consumer's form engine; the component owns the controls, the row labels and the keyboard path — and reordering is button-driven on purpose, so no drag gesture is required.
Filters
Each row is one filter; the order is the evaluation order.
const [filters, setFilters] = useState(["Sector: Technology"]);
<FieldArray
legend="Filters"
count={filters.length}
addLabel="Add filter"
minRows={1}
onAdd={() => setFilters([...filters, ""])}
onRemove={(index) => setFilters(filters.filter((_, i) => i !== index))}
onMove={(from, to) => setFilters(move(filters, from, to))}
>
{filters.map((filter, index) => (
<Input key={index} defaultValue={filter} aria-label={`Filter ${index + 1}`} />
))}
</FieldArray>A section with an action, in two columns
`actions` puts a control in the heading row — a switch for the whole block — and `columns` lays the fields out in a grid that collapses to one column on a narrow screen.
Universe
Which instruments are in scope.
<FormSection
title="Universe"
description="Which instruments are in scope."
actions={<Switch aria-label="Enable universe filters" />}
columns={2}
>
<Input defaultValue="Technology" aria-label="Sector" />
<Input defaultValue="North America" aria-label="Region" />
</FormSection>A fixed-length group
Omitting `onAdd` and `onRemove` makes the group fixed-length: the same layout without controls that would have nothing to do.
Split
<FieldArray legend="Split" count={3}>
<Input defaultValue="40%" aria-label="Split 1" />
<Input defaultValue="35%" aria-label="Split 2" />
<Input defaultValue="25%" aria-label="Split 3" />
</FieldArray>Usage
import { FormSection, FieldArray } from "@luc/ui/forms";<FormSection title="Identity" description="What this run is called.">
<Input defaultValue="Momentum v3" aria-label="Run name" />
</FormSection>Package setup — peer requirements, the stylesheet and the provider — is on installation.
API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
| Export | Kind | Signature |
|---|---|---|
FieldArray | 9 documented props | ({ legend, description, count, onAdd, onRemove, onMove, addLabel, minRows, children, className, ...props }: FieldArrayProps) => import("@types/react").JSX.Element |
FormSection | 4 documented props | ({ title, description, actions, columns, children, className, ...props }: FormSectionProps) => import("@types/react").JSX.Element |
FieldArray props
Type: FieldArrayProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
addLabel? | string | undefined | — | |
children | ReactNode | — | Rows are rendered by the caller with its own engine's bindings. |
count | number | — | Current row count. The array itself lives in the consumer's form engine. |
description? | ReactNode | — | |
legend | ReactNode | — | Row label, e.g. `(index) => `Attendee ${index + 1}``. |
minRows? | number | undefined | — | |
onAdd? | (() => void) | undefined | — | |
onMove? | ((from: number, to: number) => void) | undefined | — | Reordering is deliberately button-driven — no drag-and-drop in V0.4. |
onRemove? | ((index: number) => void) | undefined | — | Omit to make the group fixed-length. |
FormSection props
Type: FormSectionProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
actions? | ReactNode | — | Trailing content in the heading row — a toggle, a link, a count. |
columns? | 1 | 2 | 3 | undefined | — | Column count for the fields inside, from the `sm` breakpoint up. |
description? | ReactNode | — | |
title | ReactNode | — |
Guidelines
Do
- Group a long form with `FormSection`: a heading, a description and, from `sm` up, a column count.
- Reach for `FieldArray` when rows repeat, and keep the array in your form engine — the component owns the controls, not the data.
- Omit `onAdd` and `onRemove` for a fixed-length group: the same layout without buttons that would have nothing to do.
Don't
- Number the sections by hand (`1.`, `2.`) — the order is the structure, and a heading keeps it readable when rows move.
- Store the array inside `FieldArray`; a re-render would lose it, and a form engine already owns it.
- Add reordering where the order has no meaning.
Accessibility
A section is a real `<section>` with a heading, so a screen-reader user can jump between blocks. Every row control is a real button with a name that says what it does to which row, the row count is announced, and `minRows` stops the group from being emptied when the last row must stay.
Family
Forms
Inputs, their validation surfaces and the form lifecycle around them.
24 documented entries in this family. Open the family index
Related