> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metabind.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Scrolling and lists

> Scrollable containers, native lists, and data-driven iteration

export const PlatformStatuses = ({statuses}) => {
  const StatusBadge = ({status, label}) => {
    const styles = {
      green: {
        backgroundColor: '#dcfce7',
        color: '#166534'
      },
      orange: {
        backgroundColor: '#fed7aa',
        color: '#9a3412'
      },
      red: {
        backgroundColor: '#fecaca',
        color: '#991b1b'
      },
      gray: {
        backgroundColor: '#f3f4f6',
        color: '#4b5563'
      }
    };
    const baseStyle = {
      display: 'inline-flex',
      alignItems: 'center',
      padding: '0.125rem 0.625rem',
      borderRadius: '9999px',
      fontSize: '0.875rem',
      fontWeight: '500'
    };
    const colorStyle = styles[status] || styles.green;
    return <span style={{
      ...baseStyle,
      ...colorStyle
    }}>
        {label || status}
      </span>;
  };
  const STATUS_CONFIG = {
    supported: {
      label: "Supported",
      color: "green"
    },
    partial: {
      label: "Partial",
      color: "orange"
    },
    "not-implemented": {
      label: "Not Implemented",
      color: "gray"
    }
  };
  const renderCard = (platform, value) => {
    if (!value) return null;
    const {status, note} = typeof value === "string" ? {
      status: value
    } : value;
    const config = STATUS_CONFIG[status];
    if (!config) return null;
    const titleMap = {
      ios: "SwiftUI",
      android: "Jetpack Compose",
      web: "Web"
    };
    return <Card key={platform} title={titleMap[platform] || platform}>
          <StatusBadge status={config.color} label={config.label} />
          {note && <div style={{
      marginTop: '0.5rem',
      fontSize: '0.875rem',
      color: '#6b7280'
    }}>
              {note}
            </div>}
      </Card>;
  };
  if (statuses == null) {
    return null;
  }
  return <Columns cols="3">
      {Object.entries(statuses).map(([platform, value]) => renderCard(platform, value))}
    </Columns>;
};

export const ComposeJS = ({code, name, height}) => {
  const encodedCode = useMemo(() => {
    if (!code) return "";
    try {
      return btoa(code);
    } catch (e) {
      console.error("Failed to encode code", e);
      return "";
    }
  }, [code]);
  if (!encodedCode) {
    return null;
  }
  return <iframe src={`https://www.metabind.ai/embed?code=${encodedCode}&name=${name ?? 'Example'}`} loading="lazy" style={{
    width: "100%",
    height: height || '350px',
    border: "1px solid #e5e7eb",
    borderRadius: "var(--rounded-2xl,1rem)",
    overflow: "hidden"
  }} title="ComposeJS Preview" />;
};

`ScrollView` is the generic scrollable container — wrap any content that may exceed the available space, on either axis. `List` is the iOS-native scrollable list with row chrome, separators, and optional selection tracking. `ForEach` iterates over data to produce one child per item, and is what you typically pair with the lazy stacks inside a `ScrollView` or with sections inside a `List`.

## ScrollView

A scrollable container for content that may exceed the available space.

```typescript theme={null}
ScrollView(children: Component): Component;
ScrollView(children: Component[]): Component;
ScrollView(props: { axis?: Axis; showsIndicators?: boolean }, children: Component[]): Component;
```

<ParamField path="children" type="Component | Component[]" required>
  The scrollable content. Typically a stack or lazy stack containing the scroll content.
</ParamField>

<ParamField path="props" type="object" optional>
  Configuration options for the scroll view.

  <Expandable title="properties">
    <ParamField path="axis" type="Axis" optional>
      The scroll direction. One of `"vertical"`, `"horizontal"`, or `"both"`. See [Axis](/bindjs/types/Axis). Defaults to `"vertical"`.
    </ParamField>

    <ParamField path="showsIndicators" type="boolean" optional>
      Whether to show scroll indicators. Defaults to `true`.
    </ParamField>
  </Expandable>
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "partial", note: "showsIndicators prop is not implemented" },
web: { status: "supported" },
}}
/>

**Vertical scrolling**

```typescript theme={null}
ScrollView([
    VStack({ spacing: 12 }, [
        ForEach(items, (item) =>
            Text(item.name).padding(8)
        )
    ])
])
```

**Horizontal scrolling**

```typescript theme={null}
ScrollView({ axis: "horizontal", showsIndicators: false }, [
    HStack({ spacing: 16 }, [
        ForEach(cards, (card) =>
            Image({ url: card.imageUrl })
                .frame({ width: 200, height: 150 })
                .cornerRadius(12)
        )
    ])
])
```

**With a lazy stack**

For large data sets, use [LazyVStack](/bindjs/components/LazyVStack) or [LazyHStack](/bindjs/components/LazyHStack) inside a ScrollView for efficient rendering:

```typescript theme={null}
ScrollView([
    LazyVStack({ spacing: 8 }, [
        ForEach(allItems, (item) =>
            HStack({ spacing: 12 }, [
                Image({ url: item.thumbnail })
                    .frame({ width: 48, height: 48 }),
                Text(item.title)
            ])
            .padding(8)
        )
    ])
])
```

**Hiding scroll indicators**

```typescript theme={null}
ScrollView({ showsIndicators: false }, [
    VStack({ spacing: 16 }, [
        ForEach(photos, (photo) =>
            Image({ url: photo.url })
                .aspectRatio(1.5)
        )
    ])
])
```

## List

A scrollable list with optional selection tracking.

```typescript theme={null}
List(children: Component[]): Component;
List<V = string>(props: { selection: V; setSelection: (value: V) => void }, children: Component[]): Component;
```

<ParamField path="children" type="Component[]" required>
  The list content. Typically contains [ForEach](#foreach) or [Section](/bindjs/components/Section) components.
</ParamField>

<ParamField path="props" type="object" optional>
  Configuration options for selection tracking.

  <Expandable title="properties">
    <ParamField path="selection" type="V" required>
      The currently selected value. Bind to a state variable.
    </ParamField>

    <ParamField path="setSelection" type="(value: V) => void" required>
      A callback invoked when the selection changes.
    </ParamField>
  </Expandable>
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: "not-implemented",
web: "not-implemented",
}}
/>

**Basic list**

```typescript theme={null}
List([
    Text("Item 1"),
    Text("Item 2"),
    Text("Item 3")
])
```

**With ForEach**

```typescript theme={null}
List([
    ForEach(items, (item) =>
        Text(item.name)
    )
])
```

**With sections**

```typescript theme={null}
List([
    Section({ header: Text("Favorites") }, [
        ForEach(favorites, (fav) => Text(fav.name))
    ]),
    Section({ header: Text("Recent") }, [
        ForEach(recent, (item) => Text(item.name))
    ])
])
```

**With selection tracking**

```typescript theme={null}
const body = () => {
    const [selected, setSelected] = useState("")

    return List(
        { selection: selected, setSelection: setSelected },
        [
            ForEach(items, (item) =>
                Text(item.name).tag(item.id)
            )
        ]
    )
}
```

<Note>
  List provides built-in scrolling — you don't need to wrap it in a [ScrollView](#scrollview). For selection tracking, each row should have a `.tag()` modifier so the list can identify which item is selected. Customize with `.listStyle()`, `.listRowBackground()`, and `.listRowSeparator()`. For a non-list scrollable layout, use [ScrollView](#scrollview) with [LazyVStack](/bindjs/components/LazyVStack).
</Note>

## ForEach

Iterates over data to produce components, creating one child per item.

```typescript theme={null}
ForEach<T>(data: T[], content: (item: T, index: number) => Component): Component;
ForEach<T>(subviews: Component, content: ({ subview: T }) => Component): Component;
```

<ParamField path="data" type="T[]" required>
  An array of items to iterate over. Each item is passed to the content function.
</ParamField>

<ParamField path="content" type="(item: T, index: number) => Component" required>
  A function that returns a component for each item. Receives the item and its index.
</ParamField>

<ParamField path="subviews" type="Component" required>
  Subviews overload: a component whose children are decomposed into individual subviews for rearranging or inspection.
</ParamField>

<ParamField path="content" type="({ subview: T }) => Component" required>
  Subviews overload: a function that receives each subview and returns a component.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "partial", note: "Subviews overload not supported" },
web: { status: "supported" },
}}
/>

**Basic iteration**

```typescript theme={null}
ForEach(["Apple", "Banana", "Cherry"], (fruit) =>
    Text(fruit)
)
```

**Using the index**

```typescript theme={null}
ForEach(items, (item, index) =>
    HStack([
        Text(`${index + 1}.`),
        Text(item.name)
    ])
)
```

**Inside a layout container**

```typescript theme={null}
VStack({ spacing: 8 }, [
    ForEach(users, (user) =>
        HStack({ spacing: 12 }, [
            Circle()
                .fill(Color("blue"))
                .frame({ width: 32, height: 32 }),
            Text(user.name)
        ])
    )
])
```

**Subview decomposition**

Use the subviews overload to inspect and rearrange children of an existing component:

```typescript theme={null}
ForEach(
    VStack([Text("A"), Text("B"), Text("C")]),
    ({ subview }) => subview.padding(8)
)
```

<Note>
  Each call to the `content` function should return exactly one component. Use [Group](/bindjs/components/Group) to return multiple components as one. The `index` parameter is zero-based. The subviews overload enables view decomposition patterns, letting you restructure existing component trees without rebuilding them.
</Note>

## See also

* [LazyVStack](/bindjs/components/LazyVStack) — lazy vertical stack for long scrollable lists
* [LazyHStack](/bindjs/components/LazyHStack) — lazy horizontal stack for carousels
* [Section](/bindjs/components/Section) — grouping with headers and footers in a List
* [Group](/bindjs/components/Group) — return multiple components as one
* [Axis](/bindjs/types/Axis) — scroll direction values
