Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

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

Rodolfo Mori5 min de leitura

@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:

bash
npx --yes sass@1.103.1 --stdin --no-source-map --style=expanded

O 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:

css
%message {
  padding: 1rem;
  border: 1px solid;
}

.success { @extend %message; border-color: #16a34a; }
.error { @extend %message; border-color: #dc2626; }
.error, .success { padding: 1rem; border: 1px solid; }

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

css
.button { padding: .75rem 1rem; }
.save {
  @extend .button;
  background: #6d28d9;
}
.button, .save { padding: 0.75rem 1rem; }

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

css
.toolbar .button:hover { color: white; }
.save { @extend .button; }
.toolbar .button:hover, .toolbar .save:hover { color: white; }

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:

css
.chip.primary { color: white; }
.badge { @extend .chip.primary; }
Error: compound selectors may no longer be extended. Consider `@extend .chip, .primary` instead. See https://sass-lang.com/d/extend-compound for details.

╷ 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:

css
.base { color: red; }
@media (min-width: 40rem) {
  .wide { @extend .base; }
}
Error: From line 1, column 1: ╷ 1 │ .base { color: red; } │ ^^^^^^ ╵ You may not @extend selectors across media queries. ╷ 3 │ .wide { @extend .base; } │ ^^^^^^^^^^^^^ ╵ - 3:11 root stylesheet

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:

css
.notice {
  @extend %missing !optional;
  color: #111827;
}
.notice { 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:

css
%button-base {
  padding: .75rem 1rem;
  font-weight: 700;
}
.button { @extend %button-base; }
.link-button { @extend %button-base; }
.link-button, .button { padding: 0.75rem 1rem; font-weight: 700; }

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.

text
HTML controlável → classe compartilhada
saída parametrizada → mixin
seletores locais equivalentes → %placeholder + @extend

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

  • sass
  • extend
  • placeholder
  • seletores
  • css gerado
  • armadilhas

Perguntas frequentes

Qual a diferença entre @extend e mixin?
Mixin copia declarações em cada chamada. @extend reescreve seletores para que compartilhem uma regra, o que altera a estrutura da cascata.
Por que usar %placeholder em vez de uma classe?
O placeholder não aparece sozinho no CSS. Ele só gera regra quando algum seletor o estende, evitando publicar uma classe utilitária acidental.
@extend sempre reduz o CSS?
Não. Em contexto complexo ele pode gerar combinações de seletores. Meça a saída e prefira uma classe compartilhada quando o HTML puder recebê-la.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

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

  1. Sass — @extend — sass-lang.com
  2. Sass — Breaking change: Extending compound selectors — sass-lang.com

Continue por aqui