@extend no Sass: %placeholder e onde ele explode
Por que o @extend reescreve seletores em vez de copiar regras, os três jeitos de ele quebrar seu CSS e quando ainda vale usar.
@extend não copia declarações. Ele diz ao Sass que um seletor também deve ser
tratado como outro, e o compilador reescreve as listas de seletores onde o alvo
aparece. Essa diferença permite compartilhar uma regra curta, mas também espalha
o efeito da extensão por contextos que a chamada não mostra.
Os resultados abaixo saíram deste comando:
npx --yes sass@1.103.1 --stdin --no-source-map --style=expandedO comando não escreve uma confirmação separada: nos casos válidos, o stdout
é o CSS compilado mostrado em cada seção.
Compare cada saída com a lição de mixins no Sass: ali declarações se repetem; aqui seletores se unem.
O primeiro placeholder %
Placeholder parece um seletor, mas não é emitido sozinho. .success e .error
estendem %message, então aparecem agrupados na regra base:
%message {
padding: 1rem;
border: 1px solid;
}
.success { @extend %message; border-color: #16a34a; }
.error { @extend %message; border-color: #dc2626; }.success { border-color: #16a34a; }
.error { border-color: #dc2626; }
Não existe %message no CSS. Isso evita expor uma classe que alguém poderia
usar no HTML sem conhecer o contrato. O agrupamento é econômico quando os
seletores são simples e pertencem ao mesmo módulo.
Estender classe também reescreve a classe original
É permitido estender uma classe real. A saída inclui .save na mesma lista de
.button:
.button { padding: .75rem 1rem; }
.save {
@extend .button;
background: #6d28d9;
}.save { background: #6d28d9; }
Isso acopla os seletores no CSS mesmo que o HTML nunca use .button em
.save. Se você controla o markup, adicionar as duas classes costuma ser mais
explícito: class="button save" deixa a composição visível no elemento.
Explosão 1: o contexto também é estendido
O Sass procura o alvo em seletores complexos. Estender .button altera a regra
de hover que estava dentro de .toolbar:
.toolbar .button:hover { color: white; }
.save { @extend .button; }A chamada em .save não menciona toolbar nem hover, mas herda os dois
contextos. Em uma base grande, um alvo genérico pode aparecer em dezenas de
arquivos e multiplicar combinações. Essa é a explosão mais perigosa: o CSS é
válido, porém o alcance surpreende.
Explosão 2: seletor composto não pode mais ser estendido
Código antigo tentava estender .chip.primary como uma unidade. Dart Sass atual
rejeita o caso:
.chip.primary { color: white; }
.badge { @extend .chip.primary; }╷ 2 │ .badge { @extend .chip.primary; } │ ^^^^^^^^^^^^^ ╵
- 2:18 root stylesheet
Estender as duas classes separadamente não significa necessariamente a mesma coisa. Antes de aceitar a sugestão mecânica, descreva a relação esperada e considere um placeholder dedicado.
Explosão 3: a fronteira de media query
Um seletor de fora não pode ser estendido de dentro de uma media query. O compilador impede que a relação atravesse a fronteira:
.base { color: red; }
@media (min-width: 40rem) {
.wide { @extend .base; }
}A correção segura é manter alvo e extensão no mesmo contexto, usar mixin para emitir declarações ou compor classes no HTML. Não mova a media query apenas para silenciar o erro; preserve o comportamento responsivo.
!optional: quando ausência é permitida
Por padrão, estender um seletor inexistente é erro, porque pode indicar typo ou
dependência não carregada. !optional declara que a ausência é intencional:
.notice {
@extend %missing !optional;
color: #111827;
}Use somente em integração opcional bem documentada. Colocar !optional em todo
alvo transforma erro útil em silêncio e pode remover estilos sem ninguém notar.
Onde placeholder ainda funciona bem
Um placeholder privado, simples e estendido por poucos seletores do mesmo módulo é previsível:
%button-base {
padding: .75rem 1rem;
font-weight: 700;
}
.button { @extend %button-base; }
.link-button { @extend %button-base; }Aqui não há contexto externo, lista longa ou media query. Ainda assim, uma classe base no HTML pode ser mais simples se o projeto controla todos os consumidores.
A matriz de decisão: classe, mixin ou @extend
Escolha classe compartilhada quando a composição é parte do markup. Use mixin
quando precisa de argumentos, @content ou declarações em contextos diferentes.
Reserve @extend para relação semântica local e estável entre seletores simples.
Há uma diferença de propriedade do código. Uma classe no HTML pertence ao componente e fica visível para quem depura o elemento. Um mixin pertence ao build e deixa declarações no seletor consumidor. Uma extensão pertence ao grafo de seletores: para entender uma regra agrupada, você precisa localizar quem estendeu quem. Quanto maior a base e mais distantes os módulos, maior o custo dessa última busca.
Em bibliotecas, mantenha placeholders privados por padrão. Expor %base convida
consumidores a acoplar seletores internos e torna uma reorganização quebra de
versão. Se o contrato precisa ser público, documente onde pode ser estendido,
quais contextos são emitidos e quais versões do compilador são suportadas.
Revisar somente o diff SCSS é insuficiente. Compile antes e depois, conte listas
de seletores e procure combinações novas. Um aumento pequeno em bytes pode ser
aceitável; uma regra inesperada em .toolbar .save:hover pode alterar produto.
Teste visualmente estados e breakpoints que mencionam o alvo, mesmo que o arquivo
editado pareça distante.
HTML controlável → classe compartilhada
saída parametrizada → mixin
seletores locais equivalentes → %placeholder + @extendA decisão considera alcance e CSS gerado, não só linhas removidas do SCSS.
Sempre procure o alvo antes de estender e confira especificidade no
CSS depois. O guia de Sass
coloca @extend no lugar certo: é ferramenta especializada, não padrão de
herança para componentes.
Para migrar um @extend arriscado, primeiro copie a saída atual como referência.
Depois experimente uma classe base no markup ou um mixin local, compile e compare
seletores e propriedades. Só remova o alvo quando todos os estados coincidirem.
Essa sequência separa mudança de arquitetura de mudança visual e permite voltar
se uma combinação escondida fazia parte do comportamento.
Perguntas frequentes
Qual a diferença entre @extend e mixin?
Por que usar %placeholder em vez de uma classe?
@extend sempre reduz o CSS?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Dart Sass 1.103.1 (dart2js 3.13.1), Node 26.3.0, macOS, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- Sass — @extend — sass-lang.com
- Sass — Breaking change: Extending compound selectors — sass-lang.com


