Skip to content
This page has been auto-translated and may contain errors.View in English

Organizando e escalando CSS

docs.scrimba.com

A primeira folha de estilos em um novo projeto é um prazer escrever. Algumas centenas de linhas depois, algo muda: você muda a cor de um botão e três outros botões mudam também, um título se recusa a se mover até você colar !important ao lado dele, e toda nova regra parece que pode quebrar uma antiga. O CSS em si ainda está correto. O que se esgotou é a estrutura que o sustenta, e essa estrutura, não a sintaxe, é o que decide se uma folha de estilos permanece funcionável em mil linhas ou cem mil.

Por que CSS fica difícil de escalar

CSS é global por padrão. Toda regra que você escreve pode alcançar qualquer elemento correspondente em qualquer lugar da página. Escreva p { color: navy; } e todo parágrafo em todo o projeto fica azul marinho, quer você tenha querido ou não. Em uma página pequena isso é conveniente. Conforme o projeto cresce, essa é a raiz da maioria dos problemas.

O problema aparece como regras colidindo. Duas folhas de estilos têm como alvo p, ou uma regra geral e uma específica ambas se aplicam ao mesmo elemento, e agora você tem que trabalhar para descobrir qual vence. Você viu o mecanismo que decide isso em como CSS funciona: o navegador resolve conflitos pela especificidade e ordem. Escalar CSS bem é basicamente sobre não criar esses conflitos em primeiro lugar.

CSS não tem escopo integrado. Uma regra é global: ela se aplica a todo elemento que corresponda ao seu seletor, em todos os arquivos que a página carrega. Nada impede que os estilos de um componente alcancem outro, porque a linguagem não tem noção de "essa regra pertence àquele componente". Essa liberdade é o que torna CSS rápido para começar e incômodo para crescer.

Conforme uma base de código cresce, duas forças pressionam contra você. Regras colidem, porque mais seletores significam mais sobreposição, e o navegador resolve cada choque com as regras de como CSS funciona: especificidade primeiro, depois ordem de origem. E a especificidade tende a subir, porque a forma mais rápida de vencer um choque hoje é escrever um seletor um pouco mais específico, o que aumenta a barra para o que quer que precise substituí-lo amanhã. Se deixado descontrolado, você termina com seletores que ninguém consegue vencer sem !important, que é o ponto em que uma folha de estilos deixa de parecer mantível.

CSS envia um espaço de nomes único, plano e global. Todo seletor compete no mesmo espaço, e a cascata, o algoritmo que escolhe uma declaração vencedora quando várias se aplicam a um elemento, resolve cada conflito por origem, depois especificidade, depois ordem de origem. Não há limite de módulo na linguagem em si, então um seletor escrito para um componente é livre para corresponder elementos em qualquer outro. Toda técnica de escala neste capítulo existe para impor um limite que a linguagem não oferece.

O modo de falha vale a pena nomear com precisão, porque os hábitos abaixo são todas defesas contra isso. Sob pressão, a correção mais rápida para uma regra que não será aplicada é tornar seu seletor mais específico: adicione um pai, encadeie outra classe, recorra a um id. Cada um desses vence o choque imediato e aumenta o piso de especificidade para tudo depois, então a próxima substituição tem que ser mais específica ainda. Isso é uma guerra de especificidade: um catraca de mão única onde seletores apenas sobem, terminando em !important porque nada mais fica para vencê-los. A saída não é vencer essas lutas mais inteligentemente, mas manter a especificidade baixa e plana o suficiente para que elas raramente comecem, e, onde a linguagem agora permite, mover a ordenação para fora da especificidade inteiramente com camadas em cascata, coberto no final deste capítulo.

css
/* Uma regra global alcançando todo parágrafo na página */
p {
  color: navy;
}

/* Uma segunda regra, em outro lugar, competindo pelos mesmos elementos */
.notice p {
  color: crimson;   /* vence dentro de .notice: mais específico */
}
JunoPor que CSS fica difícil de escalar Regras CSS são globais: uma regra pode estilizar elementos em toda a página, o que é prático no começo e bagunçado depois. Conforme um projeto cresce, regras começam a colidir, e o navegador tem que escolher um vencedor usando especificidade e ordem. A maioria de organizar CSS é sobre escrever regras que não lutam uma contra a outra em primeiro lugar.
JunoPor que CSS fica difícil de escalar Não há escopo em CSS, então toda regra é global e livre para alcançar qualquer elemento correspondente. Conforme a base de código cresce duas coisas mordem: regras colidem, e a especificidade sobe porque a correção rápida para um choque é sempre um seletor mais específico. Mantenha essa subida sob controle e o resto da escala fica muito mais fácil.
JunoPor que CSS fica difícil de escalar CSS é um espaço de nomes global único sem limite de módulo, então a cascata resolve cada choque por origem, depois especificidade, depois ordem. A armadilha é a catraca de especificidade: cada substituição rápida aumenta o piso, então a próxima tem que subir mais alto, e você termina em !important. Toda técnica neste capítulo é uma forma de manter a especificidade baixa o suficiente para que a catraca nunca comece a girar.

Mantenha a especificidade baixa e plana

O hábito único mais útil é estilizar com classes, e principalmente uma classe por vez. Uma classe como .card é rápida de aplicar, reutilizável, e indolor de substituir mais tarde se precisar, porque uma única classe é um nível baixo e gentil de especificidade.

O problema vem de duas coisas: ids e longas cadeias de seletores. Um id como #header é muito mais difícil de substituir do que uma classe, e uma cadeia como .sidebar ul li a está presa a uma estrutura exata. Prefira uma classe simples que você possa colocar em qualquer lugar:

O hábito central para manter CSS mantível é manter a especificidade baixa e plana: estilize com classes únicas, e evite as duas coisas que aumentam a especificidade, ids e cadeias de descendentes profundas. Uma única classe é o ponto ideal. É específica o suficiente para ter como alvo o que você quer dizer e fraca o suficiente para que outra classe única possa substituí-la depois sem uma luta.

Ids pontuam muito mais alto do que classes, então uma regra #id é dolorosa de substituir e o força para cima. Cadeias de descendentes longas causam um problema diferente: .sidebar nav ul li a tanto aumenta a especificidade quanto solda a regra a uma HTML estrutura exata, então quebra no momento em que a marcação muda. Dê ao elemento sua própria classe e tenha como alvo isso diretamente.

Manter a especificidade baixa e plana é o hábito que evita guerras de especificidade em vez de vencê-las. Baixa significa cada regra pontuada o menos que pode enquanto ainda seleciona corretamente; plana significa regras agrupadas em torno do mesmo peso baixo, então qualquer uma delas pode substituir qualquer outra por ordem de origem sozinha. Uma folha de estilos onde quase tudo é um seletor de classe única tem essa propriedade: nada é difícil de vencer, porque nada é pontuado acima de seus vizinhos.

Dois constructos quebram a planura e ambos valem a pena evitar por padrão. Ids contribuem uma ordem de magnitude mais especificidade do que uma classe, então uma única regra #id fica acima de qualquer pilha de regras de classe e só pode ser vencida por outro id ou por !important; estilize com classes e reserve ids para links de fragmento e ganchos JavaScript. Cadeias de descendentes profundas aumentam a especificidade por seletor e vinculam a regra a uma ascendência fixa, então .sidebar nav ul li a é tanto difícil de substituir quanto frágil: altere a marcação e ela silenciosamente para de corresponder. A metodologia na próxima seção existe principalmente para permitir que você nomeie um elemento diretamente, então você nunca chegue a uma cadeia para encontrá-lo.

css
/* Prefira: uma única classe, baixa e plana */
.nav-link {
  color: navy;
}

/* Evite: um id, difícil de substituir depois */
#nav-link {
  color: navy;
}

/* Evite: uma cadeia profunda, frágil e especificidade mais alta */
.sidebar nav ul li a {
  color: navy;
}
JunoMantenha a especificidade baixa e plana Estilize com classes, e principalmente uma classe por vez, como .card ou .nav-link. Uma única classe é rápida de reutilizar e indolor de substituir depois, que é exatamente o que você quer. Mantenha distância de ids como #header e cadeias longas como .sidebar ul li a, porque ambos são muito mais difíceis de mudar.
JunoMantenha a especificidade baixa e plana Classes únicas são o ponto ideal: específicas o suficiente para acertar o que você quer dizer, fracas o suficiente para que outra classe as substitua sem uma luta. Ids aumentam a especificidade e o arrastam para cima, e cadeias profundas como .sidebar nav ul li a quebram no momento em que a marcação muda. Quando uma regra é difícil de colocar, dê ao elemento sua própria classe e tenha como alvo isso.
JunoMantenha a especificidade baixa e plana Baixa e plana significa toda regra pontuada perto do mesmo pequeno peso, então a ordem de origem sozinha pode resolver qualquer choque. Ids ficam uma ordem de magnitude acima de classes e apenas !important ou outro id as vence, então mantenha-as para links de fragmento e ganchos JS. Cadeias profundas tanto aumentam a especificidade quanto vinculam uma regra a uma forma HTML, que é por que nomear o elemento diretamente vence procurá-lo através de seus ancestrais.

Uma convenção de nomenclatura

Uma vez que você esteja estilizando com classes, a próxima pergunta é como nomeá-las. Nomes como .blue ou .thing2 caem rápido, porque não dizem nada sobre o que a classe é para. Uma convenção de nomenclatura é uma forma acordada de nomear classes para que o nome lhe diga o que uma classe faz e onde pertence.

A amplamente usada é chamada BEM, que significa Bloco, Elemento, Modificador. Um bloco é um componente como um cartão. Um elemento é uma parte dentro dele, escrita com dois hífens baixos. Um modificador é uma variação, escrita com dois hífens:

Com classes como a unidade de estilo, a nomenclatura se torna a coisa que as mantém organizadas. Uma convenção de nomenclatura é um padrão compartilhado para nomes de classe, e seu trabalho é tornar nomes previsíveis e livres de colisão: você pode dizer a partir de um nome de classe a qual componente pertence e que parte estiliza, e dois componentes nunca reutilizam acidentalmente o mesmo nome.

A convenção mais amplamente adotada é BEM (Bloco, Elemento, Modificador). O bloco é o componente (.card), um elemento é uma parte dele unida com dois hífens baixos (.card__title), e um modificador é uma variante unida com dois hífens (.card--featured). O payoff é especificidade plana: porque cada parte recebe sua própria classe única, você nunca precisa de uma cadeia de descendentes para alcançar .card__title, então BEM e o hábito baixo-e-plano se reforçam mutuamente.

Uma convenção de nomenclatura substitui o escopo que a linguagem carece. Ao codificar um limite de componente no próprio nome da classe, ele oferece nomes livres de colisão e autodocumentados sem qualquer recurso de linguagem: leia uma classe e você sabe seu componente, sua parte, e sua variante. Qualquer convenção consistente entrega isso; o valor está na consistência, não na pontuação exata.

BEM (Bloco, Elemento, Modificador) é a mais amplamente usada. O bloco nomeia o componente (.card), um elemento nomeia uma parte com um hífen baixo duplo (.card__title), e um modificador nomeia uma variante com um hífen duplo (.card--featured). O que faz BEM ganhar seu peso além da nomenclatura é que mantém a especificidade plana por construção: cada elemento recebe seu próprio seletor de classe única, então você endereça .card__title diretamente em vez de escrever .card .title, e toda regra no componente fica no mesmo peso. Essa é a mesma propriedade baixa-e-plana da seção anterior, agora caindo do esquema de nomenclatura gratuitamente. O custo é listas de classe verbosas no HTML, que é uma troca que a maioria das equipes aceita pela previsibilidade que compra.

css
/* Bloco: o componente em si */
.card { }

/* Elemento: uma parte do bloco, dois hífens baixos */
.card__title { }
.card__body { }

/* Modificador: uma variante do bloco, dois hífens */
.card--featured { }
html
<article class="card card--featured">
  <h2 class="card__title">Oficina do fim de semana</h2>
  <p class="card__body">Uma breve introdução ao layout.</p>
</article>
JunoUma convenção de nomenclatura Uma convenção de nomenclatura é uma forma acordada de nomear classes para que o nome diga o que é para. BEM é a comum: um bloco como .card, um elemento dentro dele como .card__title com dois hífens baixos, e uma variante como .card--featured com dois hífens. Você não tem que usar BEM, mas escolha algum esquema consistente e mantenha-o.
JunoUma convenção de nomenclatura Uma convenção torna nomes de classe previsíveis e livres de colisão, então um nome diz seu componente e sua parte. BEM é a amplamente usada: bloco .card, elemento .card__title, modificador .card--featured. Também mantém a especificidade plana, porque cada parte recebe sua própria classe única em vez de uma cadeia de descendentes.
JunoUma convenção de nomenclatura Uma convenção permanece no lugar do escopo que CSS nunca lhe deu: o limite do componente vive no nome da classe, então nomes permanecem livres de colisão e autodocumentados. BEM codifica bloco, elemento, e modificador como .card, .card__title, .card--featured, e seu payoff real é especificidade plana por construção, já que cada parte é uma única classe. O preço é listas de classe verbosas na marcação, que geralmente vale a previsibilidade.

Estruturando os arquivos

Conforme uma folha de estilos cresce, um arquivo longo fica difícil de se mover. A correção comum é dividir CSS em algumas pastas pelo que as regras fazem: estilos base (os padrões para elementos simples como body e títulos), componentes (os cartões, botões, e outras peças), e utilitários (pequenos auxiliares de propósito único como uma classe de espaçamento ou alinhamento de texto).

Um detalhe importa quando você divide os arquivos: a ordem em que você os carrega. Quando duas regras têm a mesma especificidade, a que vem depois vence, então uma folha de estilos carregada depois pode substituir uma anterior. Carregue seus arquivos do mais geral para o mais específico:

Dividir CSS em pastas por função mantém uma base de código em crescimento navegável. Uma estrutura comum é três grupos: base (resets e padrões de elemento como body, títulos, e links), componentes (peças autossuficientes como .card e .btn), e utilitários (auxiliares de propósito único como .text-center ou .mt-4).

A ordem em que você concatena ou importa esses arquivos não é cosmética, porque a ordem de origem é um desempatador em cascata: quando duas regras têm especificidade igual, a posterior vence. Então você carrega do menos ao mais específico, base primeiro, depois componentes, depois utilitários por último, então que um utilitário pode substituir um componente e um componente pode substituir um padrão base sem ninguém aumentando a especificidade para forçá-lo. Acertar a ordem é o que permite que você mantenha tudo em especificidade de classe única e ainda tenha substituições chegarexatamente onde você espera.

Estruturar arquivos por função é como você mantém CSS baixo-e-plano navegável em escala, e a ordem de carregamento é suportada por carga. Uma divisão convencional é base (resets e padrões de elemento simples), componentes (peças encapsuladas, uma por arquivo), e utilitários (auxiliares atômicos de propriedade única). A regra de ordenação segue diretamente da cascata: com especificidade mantida plana de propósito, a ordem de origem se torna o desempatador primário, então a sequência em que os arquivos carregam decide qual regra de peso igual vence.

É por isso que a ordem corre do menos ao mais específico em intenção, base, depois componentes, depois utilitários, então que grupos posteriores podem substituir anteriores sem qualquer aumento de especificidade. Um utilitário .text-center deve vencer o alinhamento de texto do próprio componente, e ele pode, puramente porque carrega por último no mesmo peso. Isso funciona, mas é uma convenção reforçada por disciplina: nada na linguagem evita alguém importando utilitários antes de componentes e silenciosamente invertendo o esquema todo. Confiar em ordem de origem em uma grande equipe é frágil exatamente por essa razão, que é o que motiva tornar a ordenação explícita em vez de posicional, o assunto da última seção.

css
/* main.css: a ordem corre do menos ao mais específico */
@import "base/reset.css";        /* padrões de elemento */
@import "base/typography.css";

@import "components/card.css";    /* peças autossuficientes */
@import "components/button.css";

@import "utilities/spacing.css";  /* carregado por último então pode substituir */
@import "utilities/text.css";
JunoEstruturando os arquivos Divida CSS em pastas pelo que as regras fazem: base para padrões de elemento, componentes para as peças como cartões e botões, e utilitários para pequenos auxiliares. Depois observe a ordem em que você carrega, porque quando a especificidade empata, a regra posterior vence. Carregue geral primeiro e específico por último, então os auxiliares podem substituir as peças.
JunoEstruturando os arquivos Agrupe arquivos por função: base, componentes, depois utilitários. Carregue-os do menos ao mais específico, porque a ordem de origem é o desempatador quando a especificidade é igual, então um utilitário carregado por último pode substituir um componente sem qualquer aumento de especificidade. Acertar a ordem é o que permite que você mantenha tudo em peso de classe única e ainda tenha substituições chegar.
JunoEstruturando os arquivos Uma vez que a especificidade é plana de propósito, a ordem de origem se torna o desempatador primário, então a ordem de carregamento de arquivo decide qual regra de peso igual vence. Base, depois componentes, depois utilitários significa grupos posteriores substituem anteriores gratuitamente, sem aumento de especificidade necessário. A pegadinha é que é convenção mantida por disciplina: importe utilitários muito cedo e você inverte o esquema todo, que é exatamente por que o próximo passo torna a ordenação explícita.

Tornando a cascata explícita

Tudo até agora mantém a cascata gerenciável por convenção: especificidade baixa, bons nomes, ordem de arquivo cuidadosa. Há mais aqui conforme você vai mais fundo, e vale a pena saber a direção de viagem. CSS moderno permite que você assuma o controle da ordenação diretamente em vez de confiar em onde um arquivo acontece de estar, e oferece maneiras de manter a cascata previsível conforme mais pessoas trabalham na mesma folha de estilos. Você conhecerá essas ferramentas conforme seus projetos crescem, e os hábitos acima são o que o preparam para elas.

As técnicas até agora gerenciam a cascata indiretamente, através de especificidade e ordem de origem. CSS mais recente permite que você a gerencie diretamente. Uma camada em cascata, escrita com a regra @layer, é um balde de ordenação nomeado: você declara as camadas antecipadamente na ordem que quer que vençam, e toda regra dentro de uma camada vence toda regra em uma camada anterior, independentemente de especificidade.

css
/* Declare a ordem vencedora uma vez, mais perdedora para mais vencedora */
@layer base, components, utilities;

@layer components {
  .card { padding: 16px; }
}

@layer utilities {
  .p-0 { padding: 0; }   /* vence sobre .card apesar de especificidade igual */
}

Porque uma camada posterior sempre vence, você não precisa mais da ordem de arquivo ser perfeita, e você raramente precisa de !important para forçar uma substituição. Esse é o benefício prático: camadas movem ordenação para fora do território frágil de "qual arquivo carregou primeiro" e para uma declaração explícita que todos podem ler.

A ordem de origem é frágil porque é posicional: funciona até alguém reordenar uma importação. Camadas em cascata, a regra @layer, substituem isso por ordenação explícita que fica acima de especificidade na cascata. Você declara a ordem de camada uma vez, e a cascata consulta ordem de camada antes de nunca olhar para especificidade, então uma regra em uma camada posterior vence uma regra em uma anterior mesmo quando a regra anterior é mais específica. A ordenação se torna uma decisão nomeada, legível em vez de um efeito colateral da posição do arquivo.

css
/* Uma linha corrige a ordem vencedora para toda a base de código */
@layer reset, base, components, utilities;

@layer components {
  .card__title { font-size: 1.25rem; }
}

@layer utilities {
  .text-lg { font-size: 1.5rem; }   /* vence: utilities é a camada posterior */
}

Isso reformula dois hábitos. Primeiro, ele desativa a guerra de especificidade: porque a ordem de camada supera a especificidade, você para de alcançar seletores mais específicos para vencer um choque, e você quase nunca precisa de !important, cujo trabalho inteiro era escapar de uma especificidade que você não podia de outra forma vencer. (Camadas até domesticam !important em si, invertendo sua precedência então uma declaração importante em uma camada anterior vence, mas o objetivo é precisá-la tão raramente que isso permanece trivial.) Segundo, isso clarifica a escolha de longa data entre dois estilos de organização: uma abordagem utility-first, compondo páginas de muitas classes atômicas de propriedade única, e uma abordagem component, agrupando estilos por trás de uma classe semântica por peça. A maioria das bases de código de produção rodam ambas, e camadas permitem que coexistam por design: coloque componentes em uma camada components e utilitários em uma camada utilities posterior, e um utilitário confiável substitui um componente sem nenhum lado escalar a especificidade. A decisão deixa de ser "qual vence a luta em cascata" e se torna "qual se lê melhor para esse pedaço de UI", porque a cascata é resolvida pela ordem de camada, não por quem escreveu o seletor mais assertivo. Essa previsibilidade é o prêmio real conforme uma equipe cresce: a ordem vencedora é uma declaração que todos podem ler, não conhecimento tribal sobre sequência de importação. Quando você precisa compartilhar valores como cores e espaçamento entre essas camadas, propriedades customizadas permitem que você os defina uma vez em vez de duplicá-los.

JunoTornando a cascata explícita Tudo até agora tama a cascata com hábitos: especificidade baixa, nomes claros, ordem de carregamento cuidadosa. Conforme você vai mais longe, CSS oferece ferramentas para definir a ordem vencedora diretamente em vez de se basear em qual arquivo carregou primeiro. Você não precisa delas ainda, e os hábitos neste capítulo são exatamente o que o deixa pronto para elas.
JunoTornando a cascata explícita Uma camada em cascata com @layer é um balde de ordenação nomeado: declare a ordem de camada antecipadamente e uma camada posterior vence uma anterior não importa a especificidade. Isso significa que a ordem de arquivo para de ter que ser perfeita e você raramente precisa de !important para forçar uma vitória. Isso move ordenação para fora de posição de arquivo frágil e para uma linha que todos podem ler.
JunoTornando a cascata explícita Camadas em cascata ficam acima de especificidade, então uma camada posterior vence mesmo contra uma regra mais específica, que liberta a catraca de especificidade e aposentar a maioria de seus usos de !important. Elas também permitem que estilos utility-first e component coexistam: componentes em uma camada, utilitários em uma posterior, e um utilitário substitui um componente sem nenhum lado escalar. O ganho conforme uma equipe cresce é que a ordenação é uma declaração legível, não conhecimento tribal sobre sequência de importação.