Collapsible card group for repeating sets of fields. Supports required validation, custom titles, and a selectable mode that dedupes across instances.
Add team members with their contact details
npx shadcn@latest add https://sdk-components.thesqd.com/r/repeatable-group-block.jsonimport { RepeatableGroup } from "@/components/blocks/repeatable-group-block";
import type {
GroupInstance,
RepeatableField,
} from "@/components/blocks/repeatable-group-block";import * as React from "react";
import { RepeatableGroup } from "@/components/blocks/repeatable-group-block";
import type {
GroupInstance,
RepeatableField,
} from "@/components/blocks/repeatable-group-block";
const fields: RepeatableField[] = [
{ key: "name", label: "Full Name", type: "text", required: true },
{ key: "email", label: "Email", type: "email", required: true },
{
key: "role",
label: "Role",
type: "select",
required: true,
options: [
{ label: "Admin", value: "admin" },
{ label: "Editor", value: "editor" },
{ label: "Viewer", value: "viewer" },
],
},
{ key: "primary", label: "Primary contact", type: "checkbox" },
];
export function Example() {
const [contacts, setContacts] = React.useState<GroupInstance[]>([
{ name: "", email: "", role: "", primary: false },
]);
return (
<RepeatableGroup
label="Team Member"
caption="Add team members with their contact details"
required
fields={fields}
minInstances={1}
maxInstances={5}
value={contacts}
onChange={setContacts}
titleField="name"
/>
);
}RepeatableGroup
├── Header (label / caption / required indicator)
├── GroupInstance[] (collapsible cards, one per repeated entry)
│ └── Fields rendered from RepeatableField[]
└── Add button (capped by maxInstances; selectable mode dedupes options across instances)Add team members with their contact details
Request access to systems with specific permissions
Assign team members to tasks. Each task can only be selected once.
RepeatableGroup
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Header label. |
itemLabel | string | — | Per-card header prefix, shown as `{itemLabel} #{n}` (e.g. "Event #1"). Defaults to `label`. Overridden by `titleField` once it has a value. |
caption | string | — | Sub-text under the label. |
required | boolean | false | Renders required indicator + enforces minInstances. |
fields | RepeatableField[] | — | Schema rendered inside each card. |
value | GroupInstance[] | — | Controlled instance array. |
onChange | (next: GroupInstance[]) => void | — | Fired on every edit. |
minInstances | number | 0 | Minimum cards. |
maxInstances | number | — | Cap on the number of cards. |
titleField | string | — | Field key shown in the collapsed card header. |
collapsed | Record<number, boolean> | — | Controlled collapse state by index. |
onCollapsedChange | (next: Record<number, boolean>) => void | — | Collapse change handler. |
selectable | boolean | false | Each card picks from a shared option pool, deduped across instances. |
selectableOptions | { label: string; value: string }[] | — | Options for selectable mode. |
selectableKey | string | — | Field key bound to the picked option. |
RepeatableField
| Prop | Type | Default | Description |
|---|---|---|---|
key | string | — | Maps to the value key inside each instance. |
label | string | — | Form label. |
caption | string | — | Helper text under the field label. |
type | "text" | "email" | "phone" | "textarea" | "select" | "multiSelect" | "checkbox" | "checkboxGroup" | "toggle" | — | Renders the matching control. |
options | { label: string; value: string }[] | — | Required for select / multiSelect / checkboxGroup. |
placeholder | string | — | Input placeholder. |
required | boolean | false | Validates the field on submit. |