# Product Shelf

Product Shelf — Legend List documentation.

Source: https://legend.so/list/v3/react/examples/product-shelf/

Legend List v3 documentation. Current documentation version.

Features used

-   `numColumns` to reflow the shelf between different column counts.
-   `overrideItemLayout` to let section headers span the full row while products stay in the grid.
-   `estimatedItemSize` to give the mixed grid a rough initial size before real measurements are available.
-   `stickyHeaderIndices` to keep section headers pinned while the mixed grid scrolls.

example-web/src/examples/ProductShelfExample.tsx

[View source in legend-list](https://github.com/LegendApp/legend-list/blob/a71f2cf68b30061f72bc0735b1326c6a21e2f762/example-web/src/examples/ProductShelfExample.tsx)

```tsx
import React from "react";

import { LegendList } from "@legendapp/list/react";
import { buildProductShelf, type ProductCard, type ProductShelfSection } from "@examples/commerce";
import { CARD_CLASS, listViewportStyle, Shell } from "./shared";

type ShelfRow =
    | { id: string; subtitle: string; title: string; type: "header" }
    | ({ badge: string; type: "product" } & ProductCard);

function buildShelfRows(sections: ProductShelfSection[]) {
    const rows: ShelfRow[] = [];
    const stickyHeaderIndices: number[] = [];

    for (const section of sections) {
        stickyHeaderIndices.push(rows.length);
        rows.push({
            id: `${section.id}-header`,
            subtitle: `${section.items.length} featured picks`,
            title: section.title,
            type: "header",
        });

        for (const [index, item] of section.items.entries()) {
            rows.push({
                ...item,
                badge: index % 2 === 0 ? "Ready to ship" : "Popular",
                type: "product",
            });
        }
    }

    return { rows, stickyHeaderIndices };
}

export function ProductShelfExample() {
    const shelf = React.useMemo(() => buildShelfRows(buildProductShelf()), []);
    const [columns, setColumns] = React.useState(2);

    return (
        <Shell title="Product Shelf">
            <div className="flex min-h-0 flex-1 flex-col">
                <div className="mb-3 flex items-center gap-3">
                    <button
                        className="cursor-pointer rounded-full border border-gray-300 px-[14px] py-[10px] font-bold disabled:opacity-40"
                        disabled={columns <= 1}
                        onClick={() => setColumns((value) => Math.max(1, value - 1))}
                        type="button"
                    >
                        -
                    </button>
                    <div className="min-w-[96px] text-center text-[14px] font-bold text-slate-900">
                        {columns} columns
                    </div>
                    <button
                        className="cursor-pointer rounded-full border border-gray-300 px-[14px] py-[10px] font-bold disabled:opacity-40"
                        disabled={columns >= 6}
                        onClick={() => setColumns((value) => Math.min(6, value + 1))}
                        type="button"
                    >
                        +
                    </button>
                </div>
                <LegendList
                    columnWrapperStyle={{ gap: 12 }}
                    data={shelf.rows}
                    estimatedItemSize={160}
                    keyExtractor={(item) => item.id}
                    numColumns={columns}
                    overrideItemLayout={(layout, item) => {
                        if (item.type === "header") {
                            layout.span = columns;
                        }
                    }}
                    recycleItems
                    renderItem={({ item }: { item: ShelfRow }) =>
                        item.type === "header" ? (
                            <div
                                className="mb-[10px] border border-slate-300 bg-indigo-50 px-3 py-[10px]"
                                style={{
                                    borderRadius: 0,
                                }}
                            >
                                <div className="text-[18px] font-extrabold">{item.title}</div>
                                <div className="mt-1 text-[13px] text-slate-500">{item.subtitle}</div>
                            </div>
                        ) : (
                            <div
                                className={`${CARD_CLASS} min-h-[132px]`}
                                style={{
                                    background: item.color,
                                }}
                            >
                                <div className="font-extrabold">{item.title}</div>
                                <div className="mt-1.5">{item.priceLabel}</div>
                                <div className="mt-3 text-[13px] text-slate-600">{item.badge}</div>
                            </div>
                        )
                    }
                    stickyHeaderIndices={shelf.stickyHeaderIndices}
                    style={listViewportStyle}
                />
            </div>
        </Shell>
    );
}
```

[View Markdown](https://legend.so/list/v3/react/examples/product-shelf.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Product+Shelf&body=Page%3A+https%3A%2F%2Flegend.so%2Flist%2Fv3%2Freact%2Fexamples%2Fproduct-shelf%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A)
