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

# Variant Selector

> Cookbook — seleção de ProductVariant a partir de Product já resolvido, sem resolver produto no snippet.

> Quero seleção de cor e tamanho na página de produto. Como implementar com o contrato Corvex?

Referência: `seu tema (adaptado do cookbook)` + `sections/variant-selector-demo/`

Índice: [Cookbooks](https://docs-loja.usecorvex.com.br/cookbook/overview.md)

***

## Objetivo

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
Product (já resolvido)
↓
product.options + product.variants
↓
Seleção do usuário
↓
matching via variant.selectedOptions[]
↓
ProductVariant selecionada
↓
Add to Cart (opcional, no parent)
```

O **Variant Selector não resolve produto**. Não usa `product` field, UUID, handle ou `products.get()` dentro do snippet.

***

## Contratos utilizados

* [Product Contract](https://docs-loja.usecorvex.com.br/reference/contracts/product.md) — `options`, `variants`
* [Product Variant](https://docs-loja.usecorvex.com.br/data/resources/product-variant.md) — `selectedOptions`, `availableForSale`, `price`, `image`
* [Cart Contract](https://docs-loja.usecorvex.com.br/reference/contracts/cart.md) — `addItem(product, qty, variant)` (integração no demo)
* [Snippet Contract](https://docs-loja.usecorvex.com.br/reference/contracts/snippet.md)
* `@corvex/runtime` — `SectionChrome`, `useCorvexCart` (**demo section only**)

## Shape público relevante

### Product (variantes)

| Campo | Uso |
| - | - |
| `options` | `ProductOption[]` — `name`, `values` |
| `variants` | `ProductVariant[]` — matching |

### ProductVariant

| Campo | Uso |
| - | - |
| `id` | identificação / cart |
| `title` | label no parent |
| `selectedOptions` | `[{ name, value }]` — **formato canônico de matching** |
| `availableForSale` | disponibilidade da combinação |
| `price`, `compareAtPrice` | parent atualiza preço |
| `image` | parent atualiza imagem (se existir) |

**Não usar:** `stock`, `inventory`, `trackInventory` nem o objeto de preço cru da API. A disponibilidade pública é `availableForSale`. O preço público é `price.formatted`.

***

## Arquivos

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
snippets/variant-selector/
├── snippet.jsx          # UI controlada
├── matchVariant.js      # helpers locais (PUBLIC_API_CANDIDATE)
├── fixtures.js          # produtos demo/teste
└── styles.css

sections/variant-selector-demo/
├── section.jsx          # demo + add to cart
├── schema.json
└── styles.css
```

***

## Implementação

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import VariantSelector from '@corvex/snippets/variant-selector';

const [selectedVariant, setSelectedVariant] = useState(null);

<VariantSelector
  product={product}
  selectedVariant={selectedVariant}
  onVariantChange={setSelectedVariant}
/>
```

`product` MUST ser `Product` completo (via `usePage('product').product`, `data.product`, ou loader da section parent).

***

## Como funciona o matching

Formato real: **array** `variant.selectedOptions`:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
[
  { "name": "Cor", "value": "Preto" },
  { "name": "Tamanho", "value": "M" }
]
```

Normalização local (exemplo):

```js theme={"theme":{"light":"github-light","dark":"github-dark"}}
// Record para comparação — helper local do cookbook
selection["Cor"] === "Preto" && selection["Tamanho"] === "M"
```

Variant correspondente = variante cujo **todos** `selectedOptions` coincidem com a seleção completa.

***

## Uma opção

Exemplo fixture `FIXTURE_SIZE_ONLY`:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
Tamanho: P | M | G
```

Selecionar `M` → `variant.id === 'var-m'`.

***

## Múltiplas opções

Fixture `FIXTURE_COLOR_SIZE`:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
Cor: Preto | Branco
Tamanho: P | M | G
```

`Preto + M` → variante existe mas `availableForSale === false`.

`Branco + M` → variante disponível com preço/imagem próprios.

***

## Availability

Somente `variant.availableForSale` (contrato público).

| Situação | UI |
| - | - |
| Combinação não existe em `variants` | botão `disabled` (`is-impossible`) |
| Combinação existe, variant indisponível | selecionável com hint "Esgotado" |
| Combinação disponível | estado normal |

Disponibilidade considera **combinação parcial** — selecionar `Preto` não desabilita `M` globalmente se `Branco/M` ainda é possível.

***

## Integração com preço / imagem

Responsabilidade do **parent**, não do selector:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
VariantSelector → selectedVariant
Parent → selectedVariant.price / compareAtPrice / image ?? product.featuredImage
```

O demo `VariantInfo` no section mostra esse padrão.

***

## Integração com Add to Cart

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

const { addItem, mutating } = useCorvexCart();

const canAdd = selectedVariant?.availableForSale && product?.id;

<button
  type="button"
  disabled={!canAdd || mutating}
  onClick={() => addItem(product, 1, selectedVariant)}
>
  Adicionar ao carrinho
</button>
```

Proteções:

* sem `selectedVariant` válida → não chamar `addItem`
* `!selectedVariant.availableForSale` → botão desabilitado

Se a API rejeitar a variante, o cliente pode gravar a linha sem o id escolhido e o botão ainda parece sucesso. Trate `error` de `useCorvexCart` e confira `productVariantId` na linha. Detalhe: [Ações do carrinho](https://docs-loja.usecorvex.com.br/data/actions/cart.md).

***

## Comportamento no Editor

* Snippet não importa APIs de editor.
* Demo section usa fixtures embutidas — funciona em `/preview` e editor sem resolver produto real.
* `SectionChrome` apenas na section demo (PUBLIC\_EDITOR\_INTEGRATION).

***

## Edge cases

| Caso | Comportamento |
| - | - |
| `variants: []` | mensagem "não possui variantes" |
| `options: []`, 1 variant | sem controles; emite única variant |
| seleção incompleta | `selectedVariant` null até combinação completa |
| combinação inválida | `findVariantForSelection` → null |
| `product` null | mensagem segura |

***

## Estado inicial

**Sem regra de plataforma.** Este exemplo usa `getExampleDefaultSelection`:

> primeira variante com `availableForSale`, senão primeira da lista.

Marcado como **estratégia do exemplo** em `matchVariant.js`.

***

## Accessibility

* `fieldset` + `legend` por opção
* `role="group"` nos valores
* botões com `aria-pressed`
* `disabled` real para combinações impossíveis
* `:focus-visible` nos botões

***

## Checklist

* [ ] `product` já resolvido antes do snippet
* [ ] matching via `selectedOptions[]` público
* [ ] availability via `availableForSale`
* [ ] parent atualiza preço/imagem
* [ ] `addItem(product, 1, variant)` com guards
* [ ] `corvex validate .`

***

## Veja também

* [Product Card](https://docs-loja.usecorvex.com.br/cookbook/product-card.md) — UI de produto sem variantes
* [Featured Product](https://docs-loja.usecorvex.com.br/cookbook/featured-product.md) — resolução de produto (contrato separado)


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