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

# Layout — iOS extensions

> Two-dimensional grids and adaptive containers — iOS-only extensions to the layout system

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" />;
};

These layout containers extend the core stack primitives with two-dimensional and adaptive behavior, and are currently iOS-only. `Grid` arranges children in a row-and-column matrix, with `GridRow` defining each row. `ViewThatFits` picks the first of several alternative layouts that fits the available space, useful for responsive designs without a manual geometry reader.

## Grid

Arranges children in a two-dimensional grid of rows and columns.

```typescript theme={null}
Grid(children: Component[]): Component;
Grid(props: { alignment?: Alignment; horizontalSpacing?: number; verticalSpacing?: number }, children: Component[]): Component;
```

<ParamField path="children" type="Component[]" required>
  An array of [GridRow](#gridrow) components. Each GridRow defines one row in the grid. The number of columns is determined by the row with the most children.
</ParamField>

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

  <Expandable title="properties">
    <ParamField path="alignment" type="Alignment" optional>
      The default alignment for all cells. See [Alignment](/bindjs/types/Alignment). Individual rows can override vertical alignment via their own `alignment` prop.
    </ParamField>

    <ParamField path="horizontalSpacing" type="number" optional>
      The horizontal distance in points between columns.
    </ParamField>

    <ParamField path="verticalSpacing" type="number" optional>
      The vertical distance in points between rows.
    </ParamField>
  </Expandable>
</ParamField>

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

**Basic grid**

```typescript theme={null}
Grid([
    GridRow([Text("Name"), Text("Value")]),
    GridRow([Text("Width"), Text("100")]),
    GridRow([Text("Height"), Text("200")])
])
```

**With spacing**

```typescript theme={null}
Grid({ horizontalSpacing: 16, verticalSpacing: 8 }, [
    GridRow([
        Text("Label").bold(),
        Text("Amount").bold()
    ]),
    Divider(),
    GridRow([Text("Apples"), Text("$3.00")]),
    GridRow([Text("Oranges"), Text("$2.50")])
])
```

**Spanning columns**

Use the `.gridCellColumns()` modifier to span a cell across multiple columns:

```typescript theme={null}
Grid({ horizontalSpacing: 12, verticalSpacing: 8 }, [
    GridRow([
        Text("Full width header")
            .font("headline")
            .gridCellColumns(2)
    ]),
    GridRow([Text("Left"), Text("Right")])
])
```

**With alignment**

```typescript theme={null}
Grid({ alignment: "leading", verticalSpacing: 12 }, [
    GridRow([
        Image({ systemName: "star.fill" }),
        Text("Favorites")
    ]),
    GridRow([
        Image({ systemName: "heart.fill" }),
        Text("Liked")
    ])
])
```

<Note>
  Grid determines the number of columns from the row with the most children. Rows with fewer children leave trailing cells empty. Use [Divider](/bindjs/components/Divider) directly as a child (not inside a GridRow) to insert a horizontal separator that spans the full grid width. Grid cell modifiers (`.gridCellColumns()`, `.gridCellAnchor()`, `.gridCellUnsizedAxes()`, `.gridColumnAlignment()`) control individual cell layout behavior.
</Note>

## GridRow

Defines a row of cells within a Grid layout.

```typescript theme={null}
GridRow(children: Component[]): Component;
GridRow(props: { alignment?: VerticalAlignment }, children: Component[]): Component;
```

<ParamField path="children" type="Component[]" required>
  An array of components, one per column cell. Each child aligns with the corresponding column in the parent [Grid](#grid).
</ParamField>

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

  <Expandable title="properties">
    <ParamField path="alignment" type="VerticalAlignment" optional>
      The vertical alignment of cells in this row. One of `"top"`, `"center"`, `"bottom"`, `"firstTextBaseline"`, or `"lastTextBaseline"`. Overrides the grid-level alignment for this row.
    </ParamField>
  </Expandable>
</ParamField>

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

**Basic row**

```typescript theme={null}
Grid([
    GridRow([Text("Cell 1"), Text("Cell 2")]),
    GridRow([Text("Cell 3"), Text("Cell 4")])
])
```

**With row alignment**

```typescript theme={null}
Grid({ horizontalSpacing: 12 }, [
    GridRow({ alignment: "top" }, [
        Text("Short"),
        Text("This cell has\nmuch more\ncontent")
    ]),
    GridRow({ alignment: "bottom" }, [
        Text("Also short"),
        Text("Another\ntall cell")
    ])
])
```

**Spanning columns**

Use `.gridCellColumns()` on a child to span multiple columns:

```typescript theme={null}
Grid([
    GridRow([Text("A"), Text("B"), Text("C")]),
    GridRow([
        Text("Spans two columns")
            .gridCellColumns(2),
        Text("C")
    ])
])
```

<Note>
  GridRow must be a direct child of [Grid](#grid). Using GridRow outside a Grid has no effect. The number of children in the widest GridRow determines the column count for the entire grid. Apply `.gridCellColumns()`, `.gridCellAnchor()`, or `.gridCellUnsizedAxes()` to individual children to control cell layout.
</Note>

## ViewThatFits

Picks the first child that fits in the available space for adaptive layouts.

```typescript theme={null}
ViewThatFits(children: Component[]): Component;
ViewThatFits(props: { axes?: Axis }, children: Component[]): Component;
```

<ParamField path="children" type="Component[]" required>
  An array of alternative layouts, ordered from most preferred to least preferred. The first child that fits in the available space is rendered.
</ParamField>

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

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

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

**Responsive layout**

Provide a wide layout and a narrow fallback. ViewThatFits selects the first one that fits:

```typescript theme={null}
ViewThatFits([
    HStack({ spacing: 12 }, [
        Image({ systemName: "star.fill" }),
        Text("Favorites"),
        Text("View all your saved items")
    ]),
    VStack({ spacing: 8 }, [
        Image({ systemName: "star.fill" }),
        Text("Favorites")
    ])
])
```

**Horizontal axis only**

Test fit only along the horizontal axis:

```typescript theme={null}
ViewThatFits({ axes: "horizontal" }, [
    HStack([
        Text("Full Label Text"),
        Spacer(),
        Text("Value")
    ]),
    HStack([
        Text("Label"),
        Spacer(),
        Text("Value")
    ]),
    Text("Value")
])
```

**Adaptive button bar**

```typescript theme={null}
ViewThatFits([
    HStack({ spacing: 8 }, [
        Button("Save", () => {}),
        Button("Cancel", () => {}),
        Button("Reset", () => {})
    ]),
    VStack({ spacing: 8 }, [
        Button("Save", () => {}),
        Button("Cancel", () => {}),
        Button("Reset", () => {})
    ])
])
```

<Note>
  Children are tested in order. The first child whose ideal size fits within the available space is rendered; all others are discarded. If no child fits, the last child is used as the fallback. This is useful for building responsive layouts that adapt to different screen sizes without using [GeometryReader](/bindjs/components/GeometryReader).
</Note>

## See also

* [VStack](/bindjs/components/VStack) — vertical stack
* [HStack](/bindjs/components/HStack) — horizontal stack
* [GeometryReader](/bindjs/components/GeometryReader) — manual geometry-based layout
* [Alignment](/bindjs/types/Alignment) — alignment values for grid cells
* [Axis](/bindjs/types/Axis) — axis values for fit testing
