VTEX IO — CSS Handles & seletores permitidos
Guia prático para customização de CSS em Store Theme e apps store: personalize via CSS Handles e arquivos .css do tema, nunca ancorando no HTML de forma frágil.
Referências oficiais
- Deprecação de seletores CSS (whitelist): CSS Selectors deprecation (nota de release da VTEX)
- Biblioteca e API de handles: vtex-apps/css-handles no GitHub — gera e expõe classes de customização; documenta
useCssHandles, useCustomClasses, createCssHandlesContext
- Detalhes e lista completa (allow/deny): selectors-reference.md
- Componentes React com handles (registro, schema, blocks): skill
vtex-io-component
Regras gerais
- Customização pública = CSS Handles — classes expostas pelo componente (ex.
.container, com prefixo de app na loja) são o contrato de estilo. Quem consome o tema (loja) usa esses handles no CSS do theme ou de customização.
- Não use seletores baseados em estrutura HTML (
div > span, section:nth-child(3)) em projetos ainda não publicados: o VTEX IO CLI bloqueia seletores fora da whitelist no link (desde a política de deprecação).
- Lojas já publicadas ainda podem publicar com seletores antigos, mas o layout fica frágil a qualquer alteração de markup — a VTEX recomenda migrar tudo para Handles e seletores permitidos.
- Dependency no app que define componentes: em
manifest.json, declare "vtex.css-handles": "0.x" (e tipagem declare module 'vtex.css-handles' se aplicável). Ver vtex-io-component.
O que a VTEX permite (whitelist resumida)
| Permitido |
Exemplo |
| Seletor de classe |
.minhaClasse |
| Pseudo-classe |
:hover, :visited, :active, :disabled, :focus, :local, :not, :target, :first-child, :last-child |
| Todos os pseudo-elementos |
::before, ::after, ::placeholder |
nth-child |
apenas (even) e (odd) |
| Combinador espaço (descendente) |
.foo .bar |
Atributo data- |
[data-foo="bar"] |
:global(vtex-...) |
Apenas para selecionar nós de outro app no formato de Handle de app (ex. componentes vtex-* versionados) |
O que a VTEX não quer (e bloqueia em apps novos no link)
| Proibido / depreciado |
Exemplo |
| Seletor de tipo (tag) |
div, span, a |
| Combinadores filho/irmão |
>, +, ~ |
nth-child com número |
li:nth-child(2) |
Seletor de atributo que não seja [data-...] |
[class~="x"], [alt="x"] |
:global com classes que não sejam handles de outro app |
workaround frágil |
Texto longo e casos de borda: selectors-reference.md.
Onde fica o CSS do tema
- Arquivos no app de store theme (ex.
styles/css/vtex.*/*.css ou pastas usadas no seu projeto) — use classes que correspondam a CSS Handles renderizados na página, não invente cadeias html body div#....
- Nomes reais de classe na loja: prefixados pelo identificador do app/versão (Handles expostos); inspecione no browser para confirmar se necessário, mas escreva o CSS como
.nomeDoHandle / modificadores (abaixo) conforme a documentação do bloco.
Padrão do time — seletores fora da whitelist (workaround)
O VTEX IO CLI valida a whitelist de seletores no vtex link. Em builds reais, o output pode exibir que a validação de CSS foi ignorada para certas dependências por causa da major version do app, por exemplo:
CSS validation was skipped for the following apps because of their current major version:
- vtex.login@2.x
- vtex.my-account@1.x
Convenção deste time: concentre todos os seletores que não seriam aceitos na validação padrão (ex.: >, +, seletor de tag, nth-child(3), atributo que não seja data-, etc.) apenas nos arquivos CSS do tema cujo escopo corresponde a esses apps de vendor — a estrutura usual do Store Theme é uma pasta por app embaixo de styles/css/, por exemplo:
styles/css/vtex.login/**/*.css — customizações de Login (apenas seletores “legados” / não conformes aqui)
styles/css/vtex.my-account/**/*.css — customizações de Minha conta (idem)
Mantenha o restante do tema (home, PLP, PDP, shelf, header genérico, etc.) com seletores conformes e Handles sempre que possível.
Regras para esse CSS “legado”:
- Tratar como dívida técnica: comentar brevemente no arquivo por que o seletor não pôde ser expresso só com Handles + whitelist (ex.: “sem handle no bloco X nesta major”).
- Após upgrade de
vtex.login / vtex.my-account (ou se a validação deixar de ser ignorada), revisar o arquivo: o vtex link pode voltar a bloquear regras antigas — refatorar para Handles/whitelist nessa oportunidade.
- Não espalhar seletores não conformes em arquivos de outros fornecedores “só porque funciona hoje” — a mensagem de “validation skipped” é por app/major; o time padronizou o escape hatch nesses dois escopos conhecidos; amplie a lista só se o CLI documentar o mesmo para outro
vendor@major.
Mais detalhe e cuidados: selectors-reference.md (seção Validação ignorada por major version e escopos no tema).
API vtex.css-handles (React)
Fonte: README do repositório vtex-apps/css-handles.
useCssHandles
- Recebe um array const de identificadores de handle (strings).
- Retorna
handles (mapeia nome de handle → classe string) e withModifiers (gera sufixos --modificador nos handles).
- Opções:
classes (override vindo de props — veja useCustomClasses); migrationFrom (IDs de app ao migrar bloco de um app para outro).
Padrão mínimo:
import { useCssHandles } from 'vtex.css-handles'
const CSS_HANDLES = ['container', 'title', 'item'] as const
function MyComponent({ classes }) {
const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })
return (
<div className={handles.container}>
<h1 className={handles.title}>Title</h1>
<ul>
{items.map((x) => (
<li
key={x.id}
className={withModifiers('item', x.modifierClass)}
>
{x.label}
</li>
))}
</ul>
</div>
)
}
Vários nós com handles no mesmo bloco: o hook deve ser chamado no componente raiz (entry do block) e receber todos os handles dos filhos, OU use createCssHandlesContext para compartilhar contexto (evita prop drill de classes).
withModifiers('handle', modifier)
- Primeiro argumento: nome lógico do handle.
- Segundo: string ou array de strings; gera classes do tipo
handle--modifier.
useCustomClasses
- Permite que o componente pai mapeie nomes/aliases de classes para o filho, injetando via prop
classes no filho que usa useCssHandles. Use quando o bloco precisa "absorver" a API de handles de componentes aninhados.
createCssHandlesContext
- Cria
CssHandlesProvider + useContextCssHandles para prover handles e withModifiers a descendentes sem passar classes por cada nível. O provider envolve a árvore; componentes aninhados usam useContextCssHandles e exportam Component.cssHandles para o pai mergear a lista de handles do bloco.
Fluxo de trabalho recomendado
- Definir handles (nomes semânticos, poucos, estáveis) no(s) componente(s) do app.
- Usar o tema para estilizar
.handle e .handle--modificador com seletores da whitelist na maior parte do projeto.
- Só quando não houver alternativa (ex.: tela de login / minha conta com markup sem Handle suficiente e major com validação relaxada), colocar seletores não conformes somente nos arquivos sob
styles/css/vtex.login/ e styles/css/vtex.my-account/ (ou o par documentado no manifest/time).
- Nunca contar com ordem de
div ou nth-child(5) no DOM da loja — mesmo no CSS legado, minimizar a superfície (menos regras possível).
- Antes do link, observar a saída do CLI: se a lista de “validation skipped” mudar, revisar esses arquivos.
- Para integração de layout Figma, blocos, schema: use a skill vtex-io-component.
Checklist rápido
Erros comuns
| Problema |
Causa provável |
Ação |
| Link falha com erro de seletor CSS |
Seletor fora da whitelist |
Reescrever usando classes + handles |
| Tema "não pega" estilo |
Seletor mirando outro bloco/versão |
Conferir nome do handle e app no inspector |
| Classe duplicada / conflito |
Dois apps, mesmo identificador de handle |
Handles são únicos por app; use namespaces claros e :global(vtex-...) só conforme regra VTEX |
| Modificador não aplica |
withModifiers incorreto ou CSS sem handle--x |
Alinhar nome do modificador e regra no .css |
Se precisar de blocos, Site Editor, interfaces.json e padrão de arquivos: use vtex-io-component em conjunto com esta skill.
1---2name: vtex-css3description: VTEX IO storefront CSS — CSS Handles, compliant selectors (bit.ly/io-css-selectors), vtex.css-handles API, and team pattern for legacy selectors in app-scoped theme CSS when CLI skips validation (e.g. vtex.login, vtex.my-account). Use when customizing store theme, compliant selectors, CSS Handles, or vendor-specific CSS overrides.4---56# VTEX IO — CSS Handles & seletores permitidos78Guia prático para customização de CSS em **Store Theme** e apps `store`: personalize via **CSS Handles** e arquivos `.css` do tema, nunca ancorando no HTML de forma frágil.910## Referências oficiais1112- **Deprecação de seletores CSS (whitelist):** [CSS Selectors deprecation](https://bit.ly/io-css-selectors) (nota de release da VTEX)13- **Biblioteca e API de handles:** [vtex-apps/css-handles no GitHub](https://github.com/vtex-apps/css-handles) — gera e expõe classes de customização; documenta `useCssHandles`, `useCustomClasses`, `createCssHandlesContext`14- **Detalhes e lista completa (allow/deny):** [selectors-reference.md](selectors-reference.md)15- **Componentes React com handles** (registro, schema, blocks): skill `vtex-io-component`1617---1819## Regras gerais20211. **Customização pública = CSS Handles** — classes expostas pelo componente (ex. `.container`, com prefixo de app na loja) são o contrato de estilo. Quem consome o tema (loja) usa esses handles no CSS do **theme** ou de **customização**.222. **Não use seletores baseados em estrutura HTML** (`div > span`, `section:nth-child(3)`) em projetos ainda **não publicados**: o **VTEX IO CLI** bloqueia seletores fora da whitelist no **link** (desde a política de deprecação).233. **Lojas já publicadas** ainda podem publicar com seletores antigos, mas o layout fica frágil a qualquer alteração de markup — a VTEX **recomenda** migrar tudo para Handles e seletores permitidos.244. **Dependency no app** que define componentes: em `manifest.json`, declare `"vtex.css-handles": "0.x"` (e tipagem `declare module 'vtex.css-handles'` se aplicável). Ver `vtex-io-component`.2526---2728## O que a VTEX **permite** (whitelist resumida)2930| Permitido | Exemplo |31|---|---|32| Seletor de **classe** | `.minhaClasse` |33| Pseudo-classe | `:hover`, `:visited`, `:active`, `:disabled`, `:focus`, `:local`, `:not`, `:target`, `:first-child`, `:last-child` |34| Todos os **pseudo-elementos** | `::before`, `::after`, `::placeholder` |35| `nth-child` | apenas `(even)` e `(odd)` |36| Combinador **espaço** (descendente) | `.foo .bar` |37| Atributo `data-` | `[data-foo="bar"]` |38| `:global(vtex-...)` | Apenas para selecionar nós de **outro app** no formato de Handle de app (ex. componentes `vtex-*` versionados) |3940## O que a VTEX **não** quer (e bloqueia em apps novos no link)4142| Proibido / depreciado | Exemplo |43|---|---|44| Seletor de **tipo** (tag) | `div`, `span`, `a` |45| Combinadores **filho/irmão** | `>`, `+`, `~` |46| `nth-child` com **número** | `li:nth-child(2)` |47| Seletor de atributo **que não seja** `[data-...]` | `[class~="x"]`, `[alt="x"]` |48| `:global` com classes que **não** sejam handles de outro app | workaround frágil |4950Texto longo e casos de borda: [selectors-reference.md](selectors-reference.md).5152---5354## Onde fica o CSS do tema5556- Arquivos no app de **store theme** (ex. `styles/css/vtex.*/*.css` ou pastas usadas no seu projeto) — use classes que correspondam a **CSS Handles** renderizados na página, não invente cadeias `html body div#...`.57- Nomes reais de classe na loja: prefixados pelo identificador do app/versão (Handles expostos); inspecione no browser para confirmar se necessário, mas **escreva o CSS** como `.nomeDoHandle` / modificadores (abaixo) conforme a documentação do bloco.5859---6061## Padrão do time — seletores fora da whitelist (workaround)6263O **VTEX IO CLI** valida a whitelist de seletores no `vtex link`. Em builds reais, o output pode exibir que a validação de CSS **foi ignorada** para certas dependências por causa da **major version** do app, por exemplo:6465```text66CSS validation was skipped for the following apps because of their current major version:6768- vtex.login@2.x69- vtex.my-account@1.x70```7172**Convenção deste time:** concentre **todos** os seletores que **não** seriam aceitos na validação padrão (ex.: `>`, `+`, seletor de tag, `nth-child(3)`, atributo que não seja `data-`, etc.) **apenas** nos arquivos CSS do tema cujo **escopo** corresponde a esses apps de vendor — a estrutura usual do Store Theme é **uma pasta por app** embaixo de `styles/css/`, por exemplo:7374- `styles/css/vtex.login/**/*.css` — customizações de **Login** (apenas seletores “legados” / não conformes aqui)75- `styles/css/vtex.my-account/**/*.css` — customizações de **Minha conta** (idem)7677Mantenha o restante do tema (home, PLP, PDP, shelf, header genérico, etc.) com **seletores conformes e Handles** sempre que possível.7879**Regras para esse CSS “legado”:**8081- Tratar como **dívida técnica**: comentar brevemente no arquivo **por que** o seletor não pôde ser expresso só com Handles + whitelist (ex.: “sem handle no bloco X nesta major”).82- Após **upgrade** de `vtex.login` / `vtex.my-account` (ou se a validação deixar de ser ignorada), revisar o arquivo: o `vtex link` pode voltar a bloquear regras antigas — refatorar para Handles/whitelist nessa oportunidade.83- Não espalhar seletores não conformes em arquivos de outros fornecedores “só porque funciona hoje” — a mensagem de “validation skipped” é **por app/major**; o time padronizou o escape hatch nesses **dois** escopos conhecidos; amplie a lista só se o CLI documentar o mesmo para outro `vendor@major`.8485Mais detalhe e cuidados: [selectors-reference.md](selectors-reference.md) (seção *Validação ignorada por major version e escopos no tema*).8687---8889## API `vtex.css-handles` (React)9091Fonte: [README do repositório vtex-apps/css-handles](https://github.com/vtex-apps/css-handles).9293### `useCssHandles`9495- Recebe um array **const** de identificadores de handle (strings).96- Retorna `handles` (mapeia nome de handle → classe string) e `withModifiers` (gera sufixos `--modificador` nos handles).97- Opções: `classes` (override vindo de props — veja `useCustomClasses`); `migrationFrom` (IDs de app ao migrar bloco de um app para outro).9899Padrão mínimo:100101```tsx102import { useCssHandles } from 'vtex.css-handles'103104const CSS_HANDLES = ['container', 'title', 'item'] as const105106function MyComponent({ classes }) {107 const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })108109 return (110 <div className={handles.container}>111 <h1 className={handles.title}>Title</h1>112 <ul>113 {items.map((x) => (114 <li115 key={x.id}116 className={withModifiers('item', x.modifierClass)}117 >118 {x.label}119 </li>120 ))}121 </ul>122 </div>123 )124}125```126127**Vários nós com handles no mesmo bloco:** o hook deve ser chamado no **componente raiz (entry do block)** e receber **todos** os handles dos filhos, OU use `createCssHandlesContext` para compartilhar contexto (evita prop drill de `classes`).128129### `withModifiers('handle', modifier)`130131- Primeiro argumento: nome lógico do handle.132- Segundo: string ou array de strings; gera classes do tipo `handle--modifier`.133134### `useCustomClasses`135136- Permite que o **componente pai** mapeie nomes/aliases de classes para o filho, injetando via prop `classes` no filho que usa `useCssHandles`. Use quando o bloco precisa "absorver" a API de handles de componentes aninhados.137138### `createCssHandlesContext`139140- Cria `CssHandlesProvider` + `useContextCssHandles` para prover `handles` e `withModifiers` a descendentes sem passar `classes` por cada nível. O provider envolve a árvore; componentes aninhados usam `useContextCssHandles` e exportam `Component.cssHandles` para o pai mergear a lista de handles do bloco.141142---143144## Fluxo de trabalho recomendado1451461. **Definir handles** (nomes semânticos, poucos, estáveis) no(s) componente(s) do app.1472. **Usar o tema** para estilizar `.handle` e `.handle--modificador` com seletores **da whitelist** na maior parte do projeto.1483. **Só** quando não houver alternativa (ex.: tela de login / minha conta com markup sem Handle suficiente e major com validação relaxada), colocar seletores não conformes **somente** nos arquivos sob `styles/css/vtex.login/` e `styles/css/vtex.my-account/` (ou o par documentado no manifest/time).1494. **Nunca** contar com ordem de `div` ou `nth-child(5)` no DOM da loja — mesmo no CSS legado, minimizar a superfície (menos regras possível).1505. Antes do **link**, observar a saída do CLI: se a lista de “validation skipped” mudar, revisar esses arquivos.1516. Para integração de layout Figma, blocos, schema: use a skill **vtex-io-component**.152153---154155## Checklist rápido156157- [ ] Handles declarados e aplicados com `useCssHandles` (ou contexto) no componente certo158- [ ] Estilos do tema usam **classes** (e whitelist) — exceto o mínimo necessário nos escopos `vtex.login` / `vtex.my-account` (conforme padrão do time)159- [ ] Seletores não conformes **restrictos** a `styles/css/vtex.login/**` e `styles/css/vtex.my-account/**` (não misturar com `vtex.store-components` etc. sem alinhamento)160- [ ] `manifest.json` inclui `vtex.css-handles` se o app expõe componentes com handles161- [ ] `vtex link` conferido: mensagem de “CSS validation was skipped” alinhada com os arquivos onde há CSS legado162163---164165## Erros comuns166167| Problema | Causa provável | Ação |168|---|---|---|169| Link falha com erro de seletor CSS | Seletor fora da whitelist | Reescrever usando classes + handles |170| Tema "não pega" estilo | Seletor mirando outro bloco/versão | Conferir nome do handle e app no inspector |171| Classe duplicada / conflito | Dois apps, mesmo identificador de handle | Handles são únicos por app; use namespaces claros e `:global(vtex-...)` só conforme regra VTEX |172| Modificador não aplica | `withModifiers` incorreto ou CSS sem `handle--x` | Alinhar nome do modificador e regra no `.css` |173174Se precisar de blocos, Site Editor, `interfaces.json` e padrão de arquivos: use **vtex-io-component** em conjunto com esta skill.