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's props are typed via a generic Props<T> that discriminates on the element type of data — TypeScript narrows the prop shape automatically based on what you pass to data.

PropTypeDescription
childrenReactNodeReact children (takes priority over data)
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 for primitive items. Required when T is string | number; forbidden when T is an object.
itemKeyT-awareFor primitive T: (item: T, index: number) => string | number. For object T: keyof T | ((item: T, index) => string | number).
itemPropsobject | ((item, ext) => object)Extra props merged onto each item, optionally per-item. Callback gets T-shaped 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.).

Mode discrimination

data element typeBranchvalueNameitemKey element type
string / numberSimpleProps<T>required(item: T, index) => string | number
objectObjectProps<T>forbidden (never)keyof T | ((item: T, index) => …)
(no data, just children)ChildrenPropsforbiddenn/a
(no narrowing — generic call site)LoosePropsoptionalunion of all above

Without an explicit generic argument and without TS being able to infer T from data, the loose backwards-compatible shape is used — same surface as before the generic was introduced. Concrete data arrays narrow 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