List
Data-driven list renderer with extended item props and optional Element wrapper.
List is a data-driven component that renders arrays of items with a specified component. Each item receives extended props with positional metadata (index, first, last, odd, even).
Basic Usage
import { List } from '@vitus-labs/elements'
// Simple string array
<List
component={({ children }) => <li>{children}</li>}
data={['Apple', 'Banana', 'Cherry']}
valueName="children"
/>
// Object array
<List
component={({ name, email }) => (
<div>{name} ({email})</div>
)}
data={[
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' },
]}
itemKey="name"
/>
// React children (highest priority)
<List>
<li>Item 1</li>
<li>Item 2</li>
</List>Props
List/Iterator expose four call overloads. The active one is chosen by the element type of data; a LooseProps fallback catches everything else (forwarded/derived prop unions, heterogeneous arrays).
| Prop | Type | Description |
|---|---|---|
children | ReactNode | React children (takes priority over data at runtime) |
data | Array<T | null | undefined> | Array of data items. T is inferred at the call site from this prop. |
component | ComponentType | Component to render each item |
valueName | string | Prop name under which each primitive value is exposed. Defaults to 'children'. |
itemKey | keyof T | ((item, index) => string | number) | Stable key per item. Object branch keeps keyof T completion for direct callers. |
itemProps | object | ((item, ext) => object) | Extra props merged onto each item, optionally per-item |
wrapComponent | ComponentType | Optional wrapper around each item |
wrapProps | object | ((item, ext) => object) | Props merged onto the wrapper, optionally per-item |
rootElement | boolean | Wrap list in Element container (default: false) |
When rootElement={true}, all Element props are also available (tag, direction, gap, alignX, alignY, css, etc.).
Overload selection
data element type | Overload | Notes |
|---|---|---|
string / number | SimpleProps<T> | each item wrapped as { [valueName]: item } |
| object | ObjectProps<T> | each item spread as props; itemKey narrows to keyof T |
(no data, just children) | ChildrenProps | children required |
| anything else (forwarded unions, mixed arrays) | LooseProps | fallback — all iterator props optional |
valueName / mode props are no longer mutually exclusive at the type level. Earlier majors used ?: never markers to forbid e.g. valueName on object arrays. Those were dropped (2.5.0) because they broke a common pattern: deriving Partial<(typeof Wrapper)['$$types']> and spreading it back into JSX. The LooseProps fallback overload (2.5.0) gives forwarded/heterogeneous data a binding home. Per-mode constraints are still honoured at runtime; they're just not enforced by the type system. Trade-off documented in the iterator forwarding changeset.
Without an explicit generic and without TS inferring T from data, the loose shape applies — same surface as a plain untyped call. Concrete data arrays still select the Simple/Object overload automatically.
Extended Item Props
Each rendered item receives these additional props:
| Prop | Type | Description |
|---|---|---|
index | number | 0-based position |
position | number | 1-based position |
first | boolean | true if first item |
last | boolean | true if last item |
odd | boolean | true if position is odd (1, 3, 5...) |
even | boolean | true if position is even (2, 4, 6...) |
With Root Element
Wrap the list in an Element container for layout control:
<List
rootElement
component={Card}
data={products}
gap={16}
direction="rows"
alignX="spaceBetween"
tag="nav"
css={(css) => css`padding: 24px;`}
/>Without rootElement (default): Items render as a fragment.
With rootElement: Items render inside an Element with full layout props.
Item Props Callback
Pass dynamic props to each item based on its position:
<List
component={Card}
data={items}
itemProps={({ index, first, last, odd }) => ({
highlighted: odd,
className: first ? 'first-item' : last ? 'last-item' : '',
style: { animationDelay: `${index * 100}ms` },
})}
/>Wrapper Component
Wrap each item in a container:
<List
component={Item}
data={items}
wrapComponent={({ children, ...props }) => (
<div className="item-wrapper" {...props}>{children}</div>
)}
wrapProps={({ odd }) => ({
className: odd ? 'odd-row' : 'even-row',
})}
/>Data Modes
List classifies data arrays internally:
- Simple — All items are
stringornumber(nullish items filtered out) - Complex — All items are objects
- Mixed — Returns
null(invalid, won't render)
Simple Arrays
For arrays of primitives, use valueName to specify which prop receives the value:
<List
component={Tag}
data={['React', 'TypeScript', 'CSS']}
valueName="label"
/>
// Each Tag receives: { label: 'React' }, { label: 'TypeScript' }, etc.Object Arrays
Object properties are spread directly onto the component:
<List
component={UserRow}
data={[
{ id: 1, name: 'Alice', role: 'Admin' },
{ id: 2, name: 'Bob', role: 'User' },
]}
itemKey="id"
/>
// UserRow receives: { id: 1, name: 'Alice', role: 'Admin', index: 0, ... }Per-Item Component Override
In object arrays, each item can specify its own component via a component property:
<List
component={DefaultCard}
data={[
{ id: 1, name: 'Alice', component: SpecialCard },
{ id: 2, name: 'Bob' }, // uses DefaultCard
]}
itemKey="id"
/>The component property is consumed by the iterator and not passed to the rendered component.
Rendering Priority
children— If provided, children are rendered (data/component ignored)data+component— Data-driven rendering
When children are used, each child receives extended props (index, first, last, etc.) and can be wrapped with wrapComponent.
Without rootElement (Default)
Items render as a flat fragment — no wrapping <div>:
<List component={Item} data={items} />
// Renders: <Item /><Item /><Item /> (no wrapper)Only Iterator-specific props (children, component, data, itemKey, valueName, itemProps, wrapComponent, wrapProps) are used. Element layout props are ignored.
API Reference
Prop
Type