
O problema
Um componente de layout precisava que um elemento ocupasse toda a altura disponível na viewport. O desenvolvedor sabia que height: stretch ainda não tinha suporte universal, então adicionou as três variantes manualmente — a forma correta de lidar com isso:
.container {
height: -webkit-fill-available;
height: -moz-available;
height: stretch;
}
Algum tempo depois, testando no Safari, ele notou um comportamento inesperado. O height: -webkit-fill-available estava causando um problema visual específico naquele browser — e a suspeita era que o prefixo WebKit era o culpado. A decisão foi remover apenas aquela linha e deixar as outras duas:
.container {
/* height: -webkit-fill-available; <- removido */
height: -moz-available;
height: stretch;
}
Simples. Cirúrgico. Faz sentido.
Ele rodou o build, abriu o bundle gerado para confirmar a mudança — e height: -webkit-fill-available estava lá. Exatamente onde ele tinha removido.
A primeira reação foi checar se tinha salvo o arquivo. Tinha. Outras alterações feitas no mesmo componente apareciam corretamente no output. Só aquela linha removida insistia em voltar, como se o build estivesse ignorando a edição.
Não era cache. Não era o editor. Era o Angular — adicionando de volta uma propriedade que o desenvolvedor tinha explicitamente removido do source.
E continuaria adicionando, independente de quantas vezes ele removesse.
TL;DR
O Angular processa seu CSS através de uma ferramenta de transformação — Autoprefixer (em projetos com webpack) ou Lightning CSS (em projetos com esbuild) — que analisa suas propriedades e injeta automaticamente variantes prefixadas para garantir compatibilidade com os navegadores declarados no .browserslistrc. O resultado prático: o CSS que você escreve não é o CSS que chega ao navegador, e esse gap é uma fonte legítima de confusão quando você não sabe que ele existe.
Pré-requisitos
- Familiaridade com CSS, incluindo a ideia de vendor prefixes
- Noção básica do processo de build do Angular (o que é
ng build, o que vai para a pastadist/) - Saber o que são PostCSS e esbuild é útil, mas não obrigatório — vou explicar o essencial no caminho
Índice
- O que acontece com o seu CSS entre o source e o browser
- As duas ferramentas que fazem esse trabalho
- Por que
height: stretchvira três declarações - Outros exemplos de transformações automáticas
- O
.browserslistrc: quem decide o que é transformado - Como verificar o que está sendo gerado
- Trade-offs, limitações e contexto de uso
- Resumo e conclusão
- Questões de compreensão
- Referências
O que acontece com o seu CSS entre o source e o browser
Quando você escreve estilos num componente Angular, eles passam por uma pipeline de transformações antes de chegar ao navegador. O Angular não pega seu .css e o serve diretamente — ele processa.
Essa pipeline tem, em linhas gerais, quatro estágios:
- Resolução do componente — o Angular lê o
styleUrlsoustylesdo decorator e prepara o CSS para encapsulamento (view encapsulation) - Transformação de compatibilidade — o CSS é analisado e expandido para garantir suporte à lista de navegadores do projeto
- Minificação — no build de produção, o CSS é comprimido
- Bundling — os estilos são emitidos como parte do bundle final
O estágio 2 é o protagonista deste artigo. É nele que propriedades como height: stretch ganham seus prefixos — e onde o CSS que você vê no editor diverge do CSS que o navegador recebe.

As duas ferramentas que fazem esse trabalho
A ferramenta responsável pela transformação muda dependendo da versão do Angular e do builder configurado.
Builder com webpack (Angular ≤ 16)
Até o Angular 16, o builder padrão era baseado em webpack. Nessa configuração, o CSS passa pelo PostCSS — uma ferramenta que aplica transformações via plugins. O plugin responsável pelos prefixos é o Autoprefixer.
O Autoprefixer lê seu CSS, consulta o banco de dados do Can I Use e o arquivo .browserslistrc do projeto, e decide quais prefixos adicionar com base nos navegadores que você declarou como suporte.
Builder com esbuild (Angular ≥ 17)
A partir do Angular 17, o builder padrão passou a ser baseado em esbuild — significativamente mais rápido por ser escrito em Go. Nessa configuração, o processamento de CSS é feito pelo Lightning CSS, uma biblioteca em Rust que funciona como parser, transformador e minificador de CSS em uma ferramenta só.
O Lightning CSS faz o mesmo trabalho que o Autoprefixer fazia, mas de forma integrada ao pipeline do esbuild. Ele também respeita o .browserslistrc para decidir quais transformações aplicar.
O comportamento do ponto de vista do desenvolvedor é equivalente: em ambos os casos, propriedades modernas são expandidas em variantes prefixadas quando necessário. A diferença é de arquitetura interna e velocidade de build — não de resultado.

Por que height: stretch vira três declarações
Agora o caso concreto do início do artigo.
A propriedade height: stretch instrui o navegador a preencher todo o espaço disponível no container — descontando margens. É sutil, mas diferente de height: 100%: o 100% é relativo ao tamanho declarado do pai, enquanto stretch é relativo ao espaço que sobra depois que margens e outros elementos são considerados.
O problema é que stretch ainda não tem suporte amplo. Os navegadores implementaram esse comportamento antes da especificação ser finalizada, e cada um usou seu próprio nome:
| Variante | Contexto |
|---|---|
-webkit-fill-available | Chrome, Safari, Edge (Chromium) |
-moz-available | Firefox |
stretch | Valor padrão (suporte crescente) |
Se você escrever apenas height: stretch, navegadores que só reconhecem a variante prefixada vão ignorar essa declaração inteiramente — e o elemento não vai se comportar como esperado.
A transformação automática do Angular resolve isso:
/* O que você escreve */
.container {
height: stretch;
}
/* O que chega ao browser (dependendo do .browserslistrc) */
.container {
height: -webkit-fill-available;
height: -moz-available;
height: stretch;
}
A ordem das declarações não é aleatória: o navegador lê de cima para baixo e aplica a última declaração que ele entende. Se ele já suporta stretch, usa stretch. Se não, usa -moz-available ou -webkit-fill-available. A propriedade mais moderna sempre fica por último — é uma cascata intencional de compatibilidade.

Isso explica o mistério do início do artigo: quando o desenvolvedor removeu -webkit-fill-available do source, o Angular a reinjetou no build — porque height: stretch é, para a ferramenta de transformação, uma instrução implícita de "garanta compatibilidade com todos os browsers da sua lista". A remoção manual conflitava com essa instrução, e a ferramenta vencia sempre.
Outros exemplos de transformações automáticas
height: stretch não é caso isolado. Várias propriedades CSS modernas passam por expansão similar. Os exemplos abaixo são comuns em projetos Angular reais.
user-select
Controla se o usuário pode selecionar texto. Ainda requer prefixo para Safari mais antigo.
/* Source */
.card-header {
user-select: none;
}
/* Output */
.card-header {
-webkit-user-select: none;
user-select: none;
}
appearance
Muito usado em resets de estilo para inputs e botões — remove o visual nativo do browser.
/* Source */
button {
appearance: none;
}
/* Output */
button {
-webkit-appearance: none;
appearance: none;
}
backdrop-filter
Efeito de desfoque atrás de elementos semi-transparentes — muito usado em modais e navegações.
/* Source */
.modal-overlay {
backdrop-filter: blur(8px);
}
/* Output */
.modal-overlay {
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
}
O -webkit- ainda é necessário para versões do Safari anteriores à 18.
text-size-adjust
Controla o comportamento de ajuste automático de tamanho de texto em dispositivos móveis. Quase sempre aparece em resets CSS.
/* Source */
html {
text-size-adjust: 100%;
}
/* Output */
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
O padrão em todos esses casos é o mesmo: você escreve a propriedade padrão, sem prefixo; a ferramenta injeta as variantes que os navegadores do seu browserslist ainda precisam.
O .browserslistrc: quem decide o que é transformado
As transformações não são fixas. O que o Autoprefixer ou Lightning CSS decide adicionar depende dos navegadores que o projeto declara como suporte.
Essa configuração fica no arquivo .browserslistrc na raiz do projeto — ou na chave "browserslist" dentro do package.json. Um projeto gerado pelo Angular CLI começa com algo assim:
# .browserslistrc
last 2 Chrome versions
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
Essa lista é lida por uma biblioteca chamada Browserslist, que a traduz para um conjunto concreto de versões de navegadores. Com base nesse conjunto, a ferramenta de transformação decide, propriedade por propriedade:
"O Chrome das últimas 2 versões já suporta
stretchnativamente? Se não, injeta o prefixo."
"O Safari das últimas 2 versões já suportauser-selectsem prefixo? Se não, injeta."
Isso significa que as transformações mudam com o tempo. À medida que os navegadores evoluem e as versões antigas saem da lista, prefixos que eram necessários deixam de ser injetados — sem que você precise mudar nada no seu CSS. O source permanece o mesmo; o output encolhe sozinho.
Para ver exatamente quais browsers sua configuração atual cobre, rode no terminal:
npx browserslist
A saída lista cada browser e versão que seu browserslist resolve naquele momento. É a forma mais direta de entender o que está guiando as decisões da ferramenta.
Como verificar o que está sendo gerado
Há formas práticas de inspecionar o CSS gerado e confirmar as transformações.
Build sem minificação
ng build --configuration=development
No build de desenvolvimento, o CSS fica legível — sem compressão agressiva. Os arquivos ficam na pasta dist/. Procure os arquivos .css e abra no editor: você vai ver as propriedades exatamente como chegam ao browser.
DevTools do browser
Com a aplicação rodando via ng serve, abra o DevTools, vá em Elements e inspecione os <style> tags injetados pelo Angular. O CSS que você ver ali já está processado — com os prefixos adicionados. Você pode comparar com o seu source para ver a diferença.
Source maps
Se os source maps estiverem habilitados, o DevTools consegue mapear o CSS processado de volta para o arquivo original. Isso torna explícita a diferença entre o que você escreveu e o que foi emitido — e é útil quando você precisa entender transformações específicas.
Trade-offs, limitações e contexto de uso
O que você ganha
Compatibilidade sem esforço manual. Você escreve CSS padrão e moderno; a ferramenta cuida da compatibilidade. Não precisa memorizar quais propriedades ainda precisam de qual prefixo — nem acompanhar quando esses prefixos podem ser removidos.
Source mais limpo. Sem prefixos no source, o código reflete a intenção — não os detalhes de implementação de compatibilidade. Quando o suporte nativo chegar, não há nada pra limpar.
Sincronização com dados reais. As ferramentas consultam o Can I Use, não um conhecimento estático. As decisões refletem o estado atual do suporte, não o que era verdade quando o projeto foi criado.
O que você precisa entender
O CSS do source ≠ o CSS do output. Esse gap é uma fonte legítima de confusão quando você não sabe que ele existe. Saber que existe muda como você depura problemas: antes de assumir que o browser está ignorando uma propriedade sua, vale confirmar o que de fato está no bundle.
Você não controla a transformação por propriedade. A ferramenta age de forma global, baseada no browserslist. Se você quiser que uma propriedade específica não seja prefixada, precisaria de configuração adicional no PostCSS ou no Lightning CSS — não é algo trivial.
O .browserslistrc é o ponto de controle real. Quer menos prefixos? Restrinja a lista de browsers. Quer compatibilidade mais ampla? Expanda. É nessa configuração que você decide o trade-off entre abrangência de suporte e tamanho de bundle — não na mão.
Os prefixos são temporários por design. Eles existem enquanto os browsers na sua lista ainda precisam deles. À medida que o suporte nativo cresce e versões antigas saem do ar, o output encolhe automaticamente. Não é dívida técnica — é um mecanismo de transição.
Quando isso pode causar problema
Se o seu CSS depende de uma cascata muito específica de declarações, a injeção automática de prefixos pode alterar essa cascata de formas inesperadas. Isso é raro na prática, mas acontece. Saber que a ferramenta existe te dá o contexto para investigar quando o comportamento não é o esperado.
Resumo e conclusão
O Angular não serve seu CSS diretamente ao browser. Entre o arquivo que você escreve e o CSS que chega ao navegador, existe uma etapa de transformação — feita pelo Autoprefixer em projetos com webpack, ou pelo Lightning CSS em projetos com esbuild.
Essa ferramenta analisa suas propriedades, consulta os dados de suporte dos browsers declarados no .browserslistrc, e injeta automaticamente as variantes prefixadas necessárias. É por isso que height: stretch pode se tornar três declarações no bundle. E é por isso que propriedades removidas do source continuam aparecendo no output — elas não são resquícios de um merge mal feito; elas estão sendo reinjetadas ativamente pelo build.
O benefício é real: você escreve CSS padrão e a compatibilidade é gerenciada pela ferramenta, de forma automática e sincronizada com dados reais de suporte. O custo é um gap entre source e output que, se você não sabe que existe, vira um mistério difícil de debugar.
Agora você sabe que existe.
Questões de compreensão
-
Por que o arquivo
.browserslistrcafeta o CSS gerado no build? Qual a relação entre a lista de browsers e as decisões da ferramenta de transformação? -
Qual a diferença entre escrever
height: stretche escrever manualmente as três variantes (-webkit-fill-available,-moz-available,stretch) no seu source? Qual abordagem é mais manutenível a longo prazo, e por quê? -
Se você remover um browser antigo do
.browserslistrc, o que acontece com os prefixos que eram necessários apenas para aquele browser? Essa mudança exige alteração no seu CSS fonte? -
Por que a ordem das declarações geradas (
-webkit-fill-available,-moz-available,stretch) importa? O que aconteceria sestretchfosse declarado primeiro? -
Um colega te diz que está vendo uma propriedade no bundle que não existe em nenhum arquivo
.cssdo projeto. Qual seria seu processo de investigação para entender de onde ela veio?