vitus·labs
Elements

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).

PropTypeDescription
childrenReactNodeReact children (takes priority over data at runtime)
dataArray<T | null | undefined>Array of data items. T is inferred at the call site from this prop.
componentComponentTypeComponent to render each item
valueNamestringProp name under which each primitive value is exposed. Defaults to 'children'.
itemKeykeyof T | ((item, index) => string | number)Stable key per item. Object branch keeps keyof T completion for direct callers.
itemPropsobject | ((item, ext) => object)Extra props merged onto each item, optionally per-item
wrapComponentComponentTypeOptional wrapper around each item
wrapPropsobject | ((item, ext) => object)Props merged onto the wrapper, optionally per-item
rootElementbooleanWrap 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 typeOverloadNotes
string / numberSimpleProps<T>each item wrapped as { [valueName]: item }
objectObjectProps<T>each item spread as props; itemKey narrows to keyof T
(no data, just children)ChildrenPropschildren required
anything else (forwarded unions, mixed arrays)LoosePropsfallback — 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:

PropTypeDescription
indexnumber0-based position
positionnumber1-based position
firstbooleantrue if first item
lastbooleantrue if last item
oddbooleantrue if position is odd (1, 3, 5...)
evenbooleantrue 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 string or number (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

  1. children — If provided, children are rendered (data/component ignored)
  2. 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

On this page