Forms
Repeatable entries
Add, remove and reorder entries of one shape: work, education, profiles.
import { Repeater } from '@jsonresume/ui'Source(opens in a new tab)
Examples
CEO at Pied Piper
Engineer at Hooli
Show code
repeater/work.tsx
'use client';
import { Field, Input, Repeater, Textarea } from '@jsonresume/ui';
import { useState } from 'react';
import * as styles from './work.css';
interface Job {
id: number;
name: string;
position: string;
summary: string;
}
let nextId = 3;
export default function RepeaterWork() {
const [work, setWork] = useState<Job[]>([
{
id: 1,
name: 'Pied Piper',
position: 'CEO',
summary: 'Built a compression platform.',
},
{ id: 2, name: 'Hooli', position: 'Engineer', summary: '' },
]);
return (
<div className={styles.root}>
<Repeater
items={work}
onChange={setWork}
getKey={(job) => job.id}
create={() => ({ id: nextId++, name: '', position: '', summary: '' })}
itemLabel={(job) =>
[job.position, job.name].filter(Boolean).join(' at ') ||
'New position'
}
noun="position"
addLabel="Add position"
renderItem={(job, update) => (
<>
<Field label="Company">
<Input
value={job.name}
onChange={(e) => update({ ...job, name: e.target.value })}
/>
</Field>
<Field label="Position">
<Input
value={job.position}
onChange={(e) => update({ ...job, position: e.target.value })}
/>
</Field>
<Field label="Summary">
<Textarea
value={job.summary}
onChange={(e) => update({ ...job, summary: e.target.value })}
/>
</Field>
</>
)}
/>
</div>
);
}repeater/work.css.ts
import { style } from '@vanilla-extract/css';
export const root = style({ width: 'min(100%, 32rem)' });Accessibility
- Each entry is a region named by its title, with a heading.
- Move and remove buttons name the entry they act on; changes are announced.
Props
<Repeater>
| Prop | Type | Default | Description |
|---|---|---|---|
| create required | () => T | – | A blank entry for Add. |
| itemLabel required | (item: T, index: number) => string | – | Heading for each entry, e.g. the job title or "Untitled position". |
| items required | T[] | – | |
| onChange required | (items: T[]) => void | – | |
| renderItem required | (item: T, update: (next: T) => void, index: number) => ReactNode | – | The fields for one entry. |
| addLabel | string | Add entry | |
| getKey | ((item: T, index: number) => string | number) | (_item, i) => i | Stable keys keep focus on the right entry when reordering. |
| headingLevel | 2345 | 3 | Heading level for each entry's title, to fit the page outline. |
| noun | string | entry | Names the kind of entry in announcements, e.g. "position". |