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

# Criar Product Grid

> Golden Path — Section de grid de produtos com collection, data loaders e carrinho.

> **Card montável no editor** (imagem, título, preço por bloco, repetido em cada produto): use o cookbook [Grade com blocos de catálogo](https://docs-loja.usecorvex.com.br/cookbook/catalog-product-grid.md). Este guia cobre grade + dados; o cookbook cobre section, blocks e persistência.

Golden Path para uma **section de produtos** orientada a dados — o principal exemplo de Section que lista catálogo na Corvex.

Contratos: [Section](https://docs-loja.usecorvex.com.br/reference/contracts/section.md) · [Data](https://docs-loja.usecorvex.com.br/reference/contracts/data.md) · [Product](https://docs-loja.usecorvex.com.br/reference/contracts/product.md) · [Cart](https://docs-loja.usecorvex.com.br/reference/contracts/cart.md) · [Schema](https://docs-loja.usecorvex.com.br/reference/contracts/schema.md).

***

## 1. Section mínima

**File: `sections/product-grid/schema.json`**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "product-grid",
  "name": "Grid de produtos",
  "category": "Produtos",
  "settings": []
}
```

**File: `sections/product-grid/section.jsx`**

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
export default function ProductGridSection() {
  return (
    <section className="product-grid">
      <p>Grid de produtos</p>
    </section>
  );
}
```

`theme.json` → `"sections": [ "product-grid", … ]`

***

## 2. Schema completo

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "product-grid",
  "name": "Grid de produtos",
  "category": "Produtos",
  "data": ["products"],
  "settings": [
    {
      "id": "title",
      "type": "text",
      "label": "Título",
      "default": "Nossos produtos"
    },
    {
      "id": "subtitle",
      "type": "textarea",
      "label": "Subtítulo"
    },
    {
      "id": "limit",
      "type": "range",
      "label": "Quantidade",
      "min": 1,
      "max": 24,
      "step": 1,
      "default": 8
    },
    {
      "id": "collection",
      "type": "collection",
      "label": "Coleção (opcional)"
    }
  ]
}
```

| Field | Tipo estável | Função |
| - | - | - |
| `title` | `text` | Título da section |
| `subtitle` | `textarea` | Texto de suporte |
| `limit` | `range` | Máximo de produtos |
| `collection` | `collection` | Filtrar por coleção (UUID) |

***

## 3. Título e subtítulo

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
export default function ProductGridSection({ settings = {} }) {
  return (
    <section style={{ padding: '48px 24px', maxWidth: 1200, margin: '0 auto' }}>
      <h2 style={{ margin: '0 0 8px' }}>
        {settings.title || 'Nossos produtos'}
      </h2>
      {settings.subtitle && (
        <p style={{ margin: '0 0 24px', opacity: 0.8 }}>
          {settings.subtitle}
        </p>
      )}
    </section>
  );
}
```

***

## 4. Quantidade (`range`)

`settings.limit` é `number`. Use no fetch:

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
const limit = settings.limit ?? 8;
```

***

## 5. Seleção de Collection

Quando `settings.collection` tem UUID, filtre a listagem:

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
await storefront.products.list({
  first: limit,
  collection: settings.collection,
});
```

Sem coleção selecionada: listagem geral (sem filtro de categoria).

***

## 6. `schema.data: ["products"]`

`sections/product-grid/schema.json` é o arquivo que `corvex validate` lê. O build copia esse JSON para o componente só se o componente ainda não tiver `.schema`.

Declare `data` nesse arquivo. Não duplique o schema com `defineSection` no JSX: se o componente já tiver `.schema`, o build deixa de anexar o `schema.json` e o inspetor pode divergir do arquivo validado.

***

## 7. `defineSectionData`

`data: ["products"]` no `schema.json` já lista com `settings.limit` (ou `settings.first`, padrão 8), `settings.collection`, `settings.category`, `settings.featured` e `settings.sortBy`.

Use `defineSectionData` quando a consulta não cabe nessa chave. A runtime lê a função em `Componente.data`, não o export solto:

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { defineSectionData } from '@corvex/theme-sdk';

export const data = defineSectionData(async ({ storefront, settings }) => {
  const limit = settings.limit ?? 8;
  const options = { first: limit };

  if (settings.collection) {
    options.collection = settings.collection;
  }

  return {
    products: await storefront.products.list(options),
  };
});
```

Depois do loader, atribua `ProductGridSection.data = data`. Sem isso, a chave `products` do schema é que roda.

***

## 8. Product Contract

Produtos chegam em `data.products.nodes` (`Connection<Product>`):

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
const products = data?.products?.nodes ?? [];
```

Campos garantidos em cards: `id`, `title`, `url`, `featuredImage`, `price`, `compareAtPrice`, `availableForSale`.

[Product Contract](https://docs-loja.usecorvex.com.br/reference/contracts/product.md)

***

## 9. ProductVariant

Para grid de cards, variante **não** é obrigatória — use `product.availableForSale` e `addItem(product)` quando não há seletor.

PDP usa variantes — ver [Product Variant](https://docs-loja.usecorvex.com.br/data/resources/product-variant.md).

***

## 10. ProductCard (snippet scaffold)

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import ProductCard from '@corvex/snippets/product-card';

{products.map((product) => (
  <ProductCard key={product.id} product={product} />
))}
```

O scaffold oficial inclui `snippets/product-card/snippet.jsx`. Card usa `product.title`, `product.featuredImage`, `product.price.formatted`.

Componente `ProductCard` do SDK (`@corvex/theme-sdk`) é alternativa pública documentada no SDK overview.

***

## 11. Imagem

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
const img = product.featuredImage;
if (img?.url) {
  return (
    <img
      src={img.url}
      alt={img.altText || product.title}
      loading="lazy"
      style={{ width: '100%', aspectRatio: '1', objectFit: 'cover' }}
    />
  );
}
```

***

## 12. Preço

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
<span>{product.price.formatted}</span>
```

O starter traz `snippets/product-card`. Não há snippet `product-price` nesse tema.

***

## 13. Compare-at price

Parte do contrato `ProductSummary`:

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
{product.compareAtPrice && (
  <s>{product.compareAtPrice.formatted}</s>
)}
```

***

## 14. Variante e add to cart

Grid simples — adicionar produto sem variante explícita quando única variante:

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { useCorvexCart } from '@corvex/theme-sdk';

function ProductGridCard({ product }) {
  const { addItem, mutating } = useCorvexCart();

  return (
    <article>
      <ProductCard product={product} />
      <button
        type="button"
        disabled={!product.availableForSale || mutating}
        onClick={() => addItem(product, 1)}
      >
        Adicionar
      </button>
    </article>
  );
}
```

Com múltiplas variantes, link para `product.url` em vez de add direto.

[Cart Contract](https://docs-loja.usecorvex.com.br/reference/contracts/cart.md)

***

## 15. Loading

Com `defineSectionData` ou `schema.data`, a vitrine não monta a seção enquanto a carga não termina e ainda não há `data`. O componente não recebe essa espera: um `if (!data)` dentro dele não é a tela de carregamento.

`useCorvexData('products', params)` busca no próprio componente. Aí o estado de espera fica no retorno do hook.

***

## 16. Empty state

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
if (!products?.length) {
  return (
    <section style={{ padding: '48px 24px', maxWidth: 1200, margin: '0 auto' }}>
      <h2>{settings.title || 'Nossos produtos'}</h2>
      <p style={{ opacity: 0.7 }}>Nenhum produto encontrado.</p>
    </section>
  );
}
```

***

## 17. Responsividade

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
<div
  style={{
    display: 'grid',
    gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
    gap: 24,
  }}
>
  …
</div>
```

CSS opcional em `sections/product-grid/styles.css`.

***

## 18. Performance

* Use `limit` em `products.list({ first: limit })` — não renderize o catálogo inteiro.
* Use `loading="lazy"` em imagens de card.
* Evite buscar produto individual em loop (N+1) — use os dados da listagem.

***

## 19. Editor

* Inspector mostra fields do `schema.json`.
* Picker `collection` salva UUID.
* O canvas lê o estado aberto no editor ([Preview Contract](https://docs-loja.usecorvex.com.br/reference/contracts/preview.md)).
* **Pré-visualizar** grava o rascunho da página antes de abrir a aba.

***

## 20. Section completa

**File: `sections/product-grid/section.jsx`**

O `schema.json` da seção 2 continua obrigatório. Este arquivo não declara `schema` de novo.

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { defineSectionData, useCorvexCart } from '@corvex/theme-sdk';
import ProductCard from '@corvex/snippets/product-card';

export const data = defineSectionData(async ({ storefront, settings }) => {
  const limit = settings.limit ?? 8;
  const options = { first: limit };
  if (settings.collection) {
    options.collection = settings.collection;
  }
  return { products: await storefront.products.list(options) };
});

function GridCard({ product }) {
  const { addItem, mutating } = useCorvexCart();
  return (
    <div>
      <ProductCard product={product} />
      <button
        type="button"
        disabled={!product.availableForSale || mutating}
        onClick={() => addItem(product, 1)}
        style={{ marginTop: 8, width: '100%' }}
      >
        Adicionar
      </button>
    </div>
  );
}

function ProductGridSection({ settings = {}, data }) {
  const products = data?.products?.nodes ?? [];

  return (
    <section
      className="product-grid"
      style={{ padding: '48px 24px', maxWidth: 1200, margin: '0 auto' }}
    >
      <h2 style={{ margin: '0 0 8px' }}>
        {settings.title || 'Nossos produtos'}
      </h2>
      {settings.subtitle && (
        <p style={{ margin: '0 0 24px', opacity: 0.8 }}>{settings.subtitle}</p>
      )}

      {products.length === 0 ? (
        <p style={{ opacity: 0.7 }}>Nenhum produto encontrado.</p>
      ) : (
        <div
          style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
            gap: 24,
          }}
        >
          {products.map((product) => (
            <GridCard key={product.id} product={product} />
          ))}
        </div>
      )}
    </section>
  );
}

ProductGridSection.data = data;

export default ProductGridSection;
```

A runtime lê `ProductGridSection.data`. O export nomeado sozinho não chega ao componente. Não atribua `ProductGridSection.schema`: o build já anexa o `schema.json` quando essa propriedade está vazia.

Registrar em `theme.json` e adicionar à home em `templates/index.json` se desejar.

***

## Checklist

* [ ] `schema.json` com `name`, `category`
* [ ] `data: ['products']` ou `defineSectionData`
* [ ] Field types estáveis apenas
* [ ] `collection` opcional no list options
* [ ] Empty state
* [ ] `limit` no fetch
* [ ] Product fields só do [Product Contract](https://docs-loja.usecorvex.com.br/reference/contracts/product.md)
* [ ] `useCorvexCart().addItem` para CTA
* [ ] `corvex validate` + `corvex build`

***

## Veja também

* [Criar Theme completo](https://docs-loja.usecorvex.com.br/guides/create-theme.md)
* [Coleção](https://docs-loja.usecorvex.com.br/data/resources/category.md)
* [Schema: collection field](https://docs-loja.usecorvex.com.br/schema/fields/collection.md)
* [Product Card Cookbook](https://docs-loja.usecorvex.com.br/cookbook/product-card.md)
* [Variant Selector Cookbook](https://docs-loja.usecorvex.com.br/cookbook/variant-selector.md)
* [Cookbooks — índice](https://docs-loja.usecorvex.com.br/cookbook/overview.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.