CSS não é tão simples quanto pensa, principalmente em larga escala!

8 regras simples para uma arquitetura CSS robusta e escalável

Créditos

Esse post é um pouco diferente, na verdade, ele é um manifesto de coisas que eu aprendi durante todos meus anos de estrada escrevendo CSS em grandes e complexos projetos web. Já me perguntaram várias vezes sobre o que irei discutir aqui, que, no final, pareceu uma boa idéia escrever um documento para usar como referência.

Vou tentar manter as explicações o mais enxuto possível, mas no geral, minha lista é:

  1. Sempre prefira classes
  2. Mantenha os arquivos de um componente no mesmo lugar
  3. Use uma nomenclatura consistente
  4. Sempre use a mesma nomenclatura para nome de arquivos
  5. Evite vazamentos de estilos para fora do componente
  6. Evite vazamentos de estilos dentro do componente
  7. Respeito os limites do componente
  8. Um modo mais simples de usar estilos externos

Introdução

Se você está trabalhando com aplicações front-end, volta e meia, você precisa escrever algum estilo. E mesmo usando as ferramentas mais modernas de hoje em dia para criar essas aplicações, CSS ainda é o único jeito de adicionar estilos na web (e recentemente, até mesmo em aplicações nativas). Atualmente, existem duas categorias para gerenciar estilos:

  • Pré-processadores de CSS, que estão por aí faz anos (ferramentas como SASS, LESS, etc)
  • Bibliotecas de CSS-in-JS, que é, relativamente, uma nova abordagem (bibliotecas como free-style e muitas outras)

A escolha entre ambas as abordagens é um tópico para outro artigo, mas, de verdade, cada uma tem seus pros e contras. Dito isso, vou estar focando na primeira categoria, e caso você tenha escolhido (ou esteja trabalhando) com a segunda, talvez você não aproveite todas as dicas (umas 3~4 ainda são válidas!).

Nosso objetivo geral

Se você chegou até aqui é porque quer alcançar uma arquitetura CSS robusta e escalável. Mas, você sabe o que isso quer dizer?

  • Orientado a componentes — A melhor maneira de lidar com a complexidade da UI é separar ela em componentes. Isso também se reflete no JS, se você estiver usando uma biblioteca um pouco mais moderna, por exemplo, React (que estimula a componetização da sua UI), isso se refletirá normalmente no JS. Nós queremos uma arquitetura CSS há altura.
  • Encapsulado — Dividir a UI em componentes não irá ajudar no nosso problema se, mexendo em um estilo de um componente, acabamos com efeitos inesperados em outras partes. Um ponto fundamental do CSS, o efeito cascata e seu único escopo global para seletores, trabalham na mão contrária dessa idéia. Se você estiver familiarizado com a especificação dos Web Componente, imagine que nós queremos a isolação de estilos do Shadow DOM sem precisar se preocupar com o suporte dos navegadores (e também, caso a especificação não vá para frente).
  • Conveniente — Queremos o melhor, certo? Mas não queremos ter muito trabalho para chegar até lá. Quero dizer, não queremos deixar nossa experiência de trabalho pior do que já está (se esse for o resultado de adotar essa arquitetura). Se possível, queremos deixar ela melhor.
  • Sem efeitos inesperados — Meio relacionado com o tópico anterior, mas, nós queremos que tudo tenha um escopo local por padrão, e global quando exceção. Nós, como engenheiros, somos preguiçosos, e, para adotar algo sem muita resistência, tem que ser um passo para o caminho certo.

Pega seu marca texto e vamos continuar…


1. Sempre prefira classes

Apenas para remover o óbvio do caminho.

Não use ID (ex: #header), porque, aonde você pensa que pode ter apenas uma instância de algo, em uma escala infinita, você irá se provar errado. Um exemplo disso, um trabalho anterior, nós queríamos remover os bugs de data-binding da nossa aplicação. Nós começamos colocando duas instâncias da UI, lado a lado, no mesmo DOM, ambas conectadas e compartilhando o mesmo modelo de dados. Isso foi para provar que qualquer mudança no modelo de dados seriam refletidas na UI. Qualquer componente que você tivesse imaginado que seria único, como um cabeçalho, não era mais. Aliás, isso foi uma baita referência para achar bugs relacionados com instâncias únicas. Moral da história: Não há situação onde usar ID seja uma melhor idéia do que usar uma classe, então meu, nunca.

E claro que, você não deve usar elementos (ex: p) diretamente. É válido usá-los hierarquicamente em um componente (veremos mais sobre isso adiante), mas, sozinhos, geralmente você acaba tendo que resetar esses estilos em um componente que não quer usá-lo. Lembrando nosso objetivo geral, isso vai, basicamente, contra todos eles (orientado a componente, encapsulado e local por padrão). Claro que, algumas propriedades como font, line-height e colors (conhecidas como propriedades herdadas, do inglês inherited properties) na tag body podem ser exceção se você quiser, mas, se você levar a sério a isolação de componentes, é completamente possível não precisar dessa declaração global (veremos mais sobre isso adiante).

Com algumas exceções, seus estilos devem sempre usar classes.


2. Mantenha os arquivos de um componente no mesmo lugar

Quando estamos trabalhando em um componente, uma ajuda tremenda é se tudo relacionado ao componente — JS, CSS, testes, documentação, etc — estiverem bem próximo:

ui/
├── layout/
| ├── Header.js // JS do componente
| ├── Header.scss // CSS do componente
| ├── Header.spec.js // testes do componente
| └── Header.fixtures.json // se o teste precisar de mock
├── utils/
| ├── Button.md // documentação
| ├── Button.js // ...e por aí vai, você sacou
| └── Button.scss

Quando você está trabalhando no código, você abre seu editor, e tudo relacionado ao componente está ali, sem precisar procurar no projeto inteiro. Existe uma união natural do seu JS e CSS para formar o DOM, é válido dizer que, se você mexer em um, cedo ou tarde vai mexer no outro.

Pense nisso como o princípio de referência de localização para componentes UI. De verdade, eu já fui o cara que criava várias pastas como styles/, tests/, docs/, e foi aí que eu enxerguei, que a única razão para eu estar fazendo isso, é porque foi sempre assim que eu fiz. ¯\_(ツ)_/¯


3. Use uma nomenclatura consistente

CSS tem um único escopo global para nomes de classes e outros identificadores (como id’s, nomes para animação e etc). Assim como “nos velhos tempos do PHP”, a comunidade lidou com isso simplesmente usando longos e estruturados nomes para emular escopos (BEM é um exemplo). Nós escolhemos uma convenção de nomes e ficamos com ela até o final.

Vamos pegar um exemplo, vamos dizer que temos o componente .myapp-Header-link. Cada uma das 3 partes desse nome tem um significado:

  • myapp essa primeira parte serve para isolar nossa aplicação de outras que estiverem no mesmo DOM.
  • Header essa parte serve para isolar nosso componente de outros componentes da nossa aplicação
  • link é um nome local (dentro do escopo do nosso componente) para adicionar estilos a algo

Sendo assim, o elemento principal do componente Header, pode utilizar da classe myapp-Header. Para um componente simples, isso é tudo que você precisa.

Qualquer que seja a convenção de nome que você escolha, nós queremos ser consistentes utilizando ela. Um ponto extra sobre as 3 partes do nome da nossa classe, além de terem uma função, elas na verdade, querem significar algo. Apenas olhando para a classe, você já sabe aonde ela pertence. A nomenclatura deve ser o mapa que nos guia pela estrutura do projeto de dentro dos nossos estilos.

De agora em diante, vamos assumir o esquema de nomenclatura app-Component-class, que pessoalmente, tem funcionado muito bem em vários projetos. Mas claro, você pode escolher uma do seu gosto.


4. Sempre use a mesma nomenclatura para nome de arquivos

Essa é uma combinação lógica das duas regras anteriores (mantenha os arquivos de um componente no mesmo lugar, use uma nomenclatura consistente): Todos os arquivos que afetam um componente específico, devem conter o mesmo nome do componente. Sem exceção.

Se você estiver visualizando o projeto no navegador e algum componente está com um comportamento estranho, você clica com o botão direito e inspeciona o benedito, você verá algo como:

<div class=”myapp-Header”>…</div>

Você decora o nome do componente, volta para seu editor, usa seu atalho mágico para abrir um arquivo do projeto, começa a digitar ”head..”, e então aparece:

Essa estreita relação para componentes correspondendo com o nome de arquivos é muito útil quando você é novo no projeto e não conhece a arquitetura dele de dentro para fora. Na verdade, você não precisa conhecer, você precisa apenas achar o componente certo para poder trabalhar.

Existe uma razão natural para isso (as vezes, não é óbvia imediatamente): Um único arquivo de estilo deve conter estilos para apenas um escopo. Porque? Vamos supor que nós temos um formulário de login, que só é usado no componente Header. Do lado do JS, ele é definido no arquivo chamado Header.js e não é exportado para lugar nenhum. De início, é tentador chamar esse formulário de .myapp-LoginForm, e usar ele tanto no Header.js quanto no Header.scss. Mas vamos supor, que um novo desenvolvedor fique responsável por arrumar um pequeno erro de layout nesse formulário. No navegador dele, ele vai inspecionar o elemento para ter uma idéia de onde começar. Quando ele vai procurar no projeto, ele não consegue achar nada como LoginForm.js ou LoginForm.scss. Então ele começa utilizando grep ou qualquer magia negra possível para tentar achar o joselito do arquivo que contém essa declaração.

Conseguiu imaginar né? Neste caso, se realmente valer a pena criar um escopo para .myapp-LoginForm, separe todas as declarações em um novo componente.

Consistência vale seu peso em ouro em projetos de qualquer tamanho.


5. Evite vazamentos de estilos para fora do componente

Agora que nós estabelecemos nossa nomenclatura, precisamos usar ela para encapsular nossos componentes. Se cada componente usa um nome de classe prefixado com um escopo único, nós podemos garantir que seus estilos nunca irão afetar elementos vizinhos. Isso funciona muito bem (veremos algumas ressalvas), mas ter que digitar o escopo toda vez é bem cansativo.

Uma solução simples para isso é declarar o prefixo no início do seu arquivo. No exemplo abaixo, só precisamos digitar o escopo uma única vez:

.myapp-Header {
background: black;
color: white;
  &-link {
color: blue;
}
  &-signup {
border: 1px solid gray;
}
}

O exemplo acima está usando SASS, mas o símbolo & — talvez seja novo para você — funciona igualmente para todos os pré-processadores de CSS atuais (PostCSS, LESS e Stylus). Para completar o exemplo acima, SASS irá compilar para:

.myapp-Header {
background: black;
color: white;
}
.myapp-Header-link {
color: blue;
}
.myapp-Header-signup {
border: 1px solid gray;
}

Todos os nossos padrões funcionam bem com essa abordagem, até mesmo as diferentes classes para declarar estado nos componentes (como os modificadores BEM):

.myapp-Header {
&-signup {
display: block;
}
  &-isScrolledDown &-signup {
display: none;
}
}

Que será compilado para:

.myapp-Header-signup {
display: block;
}
.myapp-Header-isScrolledDown .myapp-Header-signup {
display: none;
}

Até mesmo media queries funcionam:

.myapp-Header {
&-signup {
display: block;
  @media (max-width: 500px) {
display: none;
}
}
}

Que se transforma em:

.myapp-Header-signup {
display: block;
}
@media (max-width: 500px) {
.myapp-Header-signup {
display: none;
}
}

Com essa abordagem, fica fácil usar nomes longos e únicos para classes, sem precisar se preocupar com a repetição da digitação desses nomes.

Conveniência é mandatório no uso das nossas ferramentas, tudo para facilitar a nossa vida diária.

Nota: Estilos e JS

Esse documento é sobre convenção de estilos, mas estilos não existem no vácuo: o JS precisa produzir o mesmo escopo de nomes e nomenclatura e conveniência também deve ser mandatório por lá.

Puxando um pouco de sardinha, eu criei algo bem simples, com zero dependências, uma pequena biblioteca JS para exatamente isso, chamada css-ns. Quando combinado com algum framework (como React), ele ajuda a garantir a combinação nome do arquivo + nomenclatura:

// Cria uma cópia local do React, limitada há sua nomenclatura:
var { React } = require('./config/css-ns')('Header');
// Criando alguns elementos:
<div className="signup">
<div className="intro">...</div>
<div className="link">...</div>
</div>

O exemplo acima vai renderizar o seguinte:

<div class="myapp-Header-signup">
<div class="myapp-Header-intro">...</div>
<div class="myapp-Header-link">...</div>
</div>

Bem conveniente, certo? E tudo isso garantido que o seu JS tenha um escopo local por padrão.

Mas, chega de JS, vamos voltar para o CSS.


6. Evite vazamentos de estilos dentro do componente

Lembra quando eu falei que prefixar o nome da classe com o nome do componente funciona muito bem para criar um escopo para os estilos? Lembra quando eu disse que havia ressalvas?

Veja os estilos a seguir:

.myapp-Header {
a {
color: blue;
}
}

E o componente com a seguinte estrutura:

+-------------------------+
| Header |
| |
| [home] [blog] [about] | <-- esses são elementos <a>
+-------------------------+

Parece tudo bem, certo? Apenas os elementos dentro do Header irão ser afetados pela cor azul, o CSS gerado é:

.myapp-Header a { color: blue; }

Mas e se o layout mudar? Imagine o cenário:

+-----------------------------------------+
| Header +-----------+ |
| | LoginForm | |
| | | |
| [home] [blog] [about] | [info] | | <-- elementos <a>
| +-----------+ |
+-----------------------------------------+

O seletor .myapp-Header a também afeta os elementos dentro do LoginForm, e aqui, nós quebramos nosso isolamento. Parece que prefixar um escopo único no nome da classe funciona muito bem para elementos vizinhos, mas não para elementos filhos.

Podemos arrumar isso de duas maneiras:

  1. Nunca usar nomes de tag’s HTML nos nossos seletores. Se tudo dentro de Header for transformado em algo como <a class=”myapp-Header-link”>, nós nunca teremos esse problema. Mas as vezes você tem sua marcação semântica criada da maneira correta, e não quer ficar adicionando classes nos elementos, nesse caso, vamos para a próxima opção:
  2. Você pode usar o seletor de elementos filhos

Se ajustarmos nosso exemplo para usar a última opção, ficaria assim:

.myapp-Header {
> a {
color: blue;
}
}

Que irá garantir o encapsulamento (e ainda podendo fazer seleções baseado na estrutura semântica do seu componente), o código gerado seria:

.myapp-Header > a { color: blue }.

Parece controverso, aliás, que tal um exemplo que irá fazer a pressão do seu sangue subir? Se eu te disser que o exemplo a seguir também é válido:

.myapp-Header {
> nav > p > a {
color: blue;
}
}

Nós fomos treinados a evitar seletores aninhados (incluindo essa forma rígida com o seletor de elementos filhos) como se fossem praga, por anos, isso foi visto como um conselho de boas práticas. Mas porque? As razões citadas podem ser resumidas em:

  1. CSS irá azedar seu dia, quase sempre. Quanto mais você aninha os seletores, maiores as chances de você, acidentalmente, encontrar um elemento que irá combinar com o seu seletor em mais de um componente. Se você leu até aqui, com certeza já sabe como evitar esse problema (prefixando o escopo no nome do componente, você pode usar seletores filhos quando precisar).
  2. Muita especificidade irá reduzir a re-usabilidade. Os estilos escritos para nav p a não serão re-usados em nenhum lugar, exceto naquele ambiente bem específico. O fato é, nós não queremos que isso aconteça, nós escolhemos uma estrutura que proíbe esse tipo de re-usabilidade, isso não funciona bem com o nosso objetivo de componentes encapsulados um do outro.
  3. Muita especificidade deixa a refatoração difícil. Isso até que faz sentido, caso você tenha mesmo que fazer. .myapp-Header-link a, você poderia remover o <a> no HTML do seu componente, e os mesmos estilos serão sempre aplicados. Se compararmos com nav > p > a, você irá precisar atualizar o seletor CSS para combinar com a nova estrutura HTML. Mas, pensando em como nós criamos pequenos componentes, encapsulados e isolados um do outro, essa afirmação é bastante discutível. Claro, se você tiver que considerar HTML & CSS para sua aplicação inteira quando estiver fazendo a refatoração, isso pode ser um pouco assustador. Mas, se os seus componentes usam um escopo próprio e são encapsulados, você terá a garantia de que nada irá afetar outros elementos fora desse escopo, sendo assim, esses tipos de mudança acabam ficando fácil.

Esse é um bom exemplo de como entender as regras faz a diferença. Porque você sabe quando quebra-las. Na nossa arquitetura, seletores aninhados não são apenas bem vistos, como as vezes, são a escolha certa. Vai fundo maluco!

Nota: Evitando vazamentos de estilos para o componente

Vamos supor que nós conseguimos um encapsulamento perfeito para nossos estilos, cada componente consegue viver em total isolamento do resto da página, certo? Vamos recapitular algumas coisas:

  • Nós prevenimos o vazamento de estilos para fora do nosso componente, prefixando cada um deles com o nome do componente:
+-------+
| |
| -----X--->
| |
+-------+

E, de quebra, prevenimos também o vazamento entre os componentes:

+-------+     +-------+
| | | |
| ------X------> |
| | | |
+-------+ +-------+

Outro ganho, prevenimos o vazamento para componentes filhos usando o seletor de elementos filhos:

+---------------------+
| +-------+ |
| | | |
| ----X------> | |
| | | |
| +-------+ |
+---------------------+

Porém, estilos externos ainda podem vazar para o componente:

      +-------+
| |
----------> |
| |
+-------+

Por exemplo, vamos dizer que que usamos o seguinte estilo:

.myapp-Header {
> a {
color: blue;
}
}

Mas, quando nós incluímos uma biblioteca CSS de terceiros com a seguinte declaração:

a {
font-family: "Comic Sans";
}

E aqui vem a afirmação mais cruel do dia, não existe uma maneira simples de proteger os componentes desse tipo de abuso externo, e é por isso que muitas vezes, quando trabalhando com CSS, nós simplesmente:

Com um pouco de sorte, você terá um certo controle sobre as dependências que você usa, e pode simplesmente encontrar uma alternativa melhor.

Como eu disse, não existe uma maneira simples de proteger seus componentes disso. Isso não quer dizer que não existem meios. Acredite, existem algumas soluções, porém elas chegam com algumas ressalvas:

  • Utilizando o modo bruto: Se você resetar o CSS para cada elemento de cada componente, e adicionar isso a um seletor que sempre irá vencer sobre as bibliotecas de terceiros, você terá uma solução. Óbvio, ao menos que sua aplicação seja pequena (vamos dizer, um botão “Compartilhar” que outras pessoas possam colocar por aí), essa abordagem rapidamente pode sair fora de controle. Essa, raramente é uma boa idéia, só coloquei aqui para paz de espírito.
  • all: initial é uma propriedade CSS não muito conhecida, criada exatamente para isso. Ela pode parar declarações herdadas de vazar para o componente, e também funciona como um reset local, contato que ela ganhe como maior especificação (e que você repita essa declaração para cada componente que você quer proteger). Sua implementação inclui algumas complexidades e ainda não é suportado por todos os navegadores, mas, all: initial pode se tornar uma poderosa ferramenta de encapsulamento.
  • Shadow DOM já foi mencionado, e é a ferramenta exata para esse trabalho, pois permite declarar limites não apenas para o CSS, mas também para o JS. Apesar de alguns trabalhos recentes na especificação, Web Components não fizeram muito progresso nos últimos anos, e ao menos que você esteja trabalhando com um ambiente bem controlado, não podemos contar com Shadow DOM ainda.
  • Finalmente, existe o <iframe>. Ele fornece o encapsulamento mais forte em tempo de execução, para web (tanto para CSS quanto para JS), mas seu custo é alto em relação a inicialização (latência de rede) e manutenção (retêm memória). Ainda assim, algumas vezes, vale a pena usá-lo. Alguns dos mais fazemos componentes embutidos (Facebook, Twitter, Disqus, etc), na verdade, são implementados usando iframes. Mas, se formos encapsular inúmeros componentes um do outro, essa abordagem pode explodir a performance da sua aplicação.

Enfim, já estamos perdendo o foco, vamos voltar para a lista de CSS.


7. Respeito os limites do componente

Assim como nós estilizamos .myapp-Header > a, quando nós aninhamos componentes, nós talvez iremos precisar aplicar alguns estilos para componentes filhos, imagine o seguinte cenário:

+---------------------------------+
| Header +------------+ |
| | LoginForm | |
| | | |
| | +--------+ | |
| +--------+ | | Button | | |
| | Button | | +--------+ | |
| +--------+ +------------+ |
+---------------------------------+

De primeira, fica claro que estilizar .myapp-Header .myapp-Button é uma má idéia, na verdade nós queremos .myapp-Header > .myapp-Button. Mas, nesse caso, quais estilos nós queremos aplicar para nossos elementos filhos?

Note que, o LoginForm está encaixado do lado direito do Header. Intuitivamente, um dos estilos seria:

.myapp-LoginForm {
float: right;
}

Nós não violamos nenhuma de nossas regras, mas também, deixamos o LoginForm bem mais difícil de se re-usar: Se uma página qualquer quiser usar o LoginForm, mas sem o float: right, estaremos sem sorte!

A solução pragmática para isso (parcialmente), é modificar nossa declaração anterior para aplicar os estilos somente no escopo que ele precisa. Ficando assim:

.myapp-Header {
> .myapp-LoginForm {
float: right;
}
}

De fato, isso até certo ponto está certo, só não podemos quebrar os estilos do componente em si:

// NÃO FAÇA ISSO
.myapp-Header {
> .myapp-LoginForm {
color: blue;
padding: 20px;
}
}

Nós não queremos permitir isso, com o código acima, LoginForm.scss não é mais o único lugar que você precisaria olhar para ver as modificações na aparência do componente LoginForm. Fazer mudanças dessa maneira volta a ser frágil, aquela sensação de que algo pode sair errado. Então, onde é que nós traçamos a linha do que pode ou não pode ser modificado?

Nós queremos respeitar os limites internos de cada componente filho, já que não queremos saber sua implementação interna. É uma caixa preta que não precisamos saber. Agora, o que está fora do componente filho, é a área do componente pai. A distinção entre dentro e fora vem de um dos conceitos mais fundamentais do CSS: o box-model.

Minhas analogias são terríveis, mas aqui vai: só significa que você está dentro de um país, quando você passa suas bordas. Estabeleceremos aqui que, um elemento pai pode afetar os estilos dos seus elementos filhos diretos até a borda do seu país. Isso quer dizer, ele deve se limitar as declarações de posicionamento e dimensão (tais como position, margin, display, width, float, z-index, etc) e propriedades que vão além da borda (tais como border, padding, color, font, etc) estão fora de questionamento.

Para deixar claro, aqui vai mais um exemplo do que não deve ser feito:

// NÃO FAÇA ISSO, JAMÉ
.myapp-Header {
> .myapp-LoginForm {
> a {
// precisa saber a implementação do LoginForm ;__;
color: blue;
}
}
}

Claro que, existem alguns casos extremos e bem chatos, como:

  • box-shadow — Um tipo de sombra pode ser parte integral do design do componente, e portanto, pensamos que ele deve conter esses estilos. Novamente, esse efeito visual é renderizado fora da borda, então, na verdade, deve ser declarado no componente pai.
  • color, font e outras propriedades herdadas — .myapp-Header > .myapp-LoginForm { color: red }, alcançam o interior do componente filho, mas por outro lado, essa outra declaração pode ser equivalente .myapp-Header { color: red }, o que também está correto.
  • display — Se o elemento filho usa Flexbox, possivelmente ele está confiando que o elemento pai contém display: flex. Entretanto, o elemento pai pode escolher se deve esconder o elemento filho usando display: none.

Esses casos extremos são as parte mais importante para se entender, você não está arriscando uma guerra nuclear com essas declarações, apenas, adicionando um pouco do efeito cascata de volta ao nosso encapsulamento. Ao contrário de muitos efeitos colaterais que o efeito cascata possa ter, usa-lo de modo moderado é benéfico. Aliás, vendo de perto o exemplo anterior, a regra de especificidade do CSS funciona perfeitamente nesse exemplo: quando o componente está visível, .myapp-LoginForm { display: flex } é a regra mais específica. Quando o elemento pai quiser esconder esse componente, .myapp-Header-loginBoxHidden > .myapp-LoginBox { display: none }, essa regra se torna mais específica que a anterior, ganhando precedência no estilo.


8. Um modo mais simples de usar estilos externos

Para evitar trabalho repetitivo, as vezes precisamos compartilhar estilos entre componentes. Para evitar isso completamente, de vez em quando procuramos estilos criados por outros. Ambas soluções devem ser alcançadas com o mínimo de acoplamento possível com sua base de código.

O que eu quero dizer? Para um exemplo concreto, vamos considerar uma das bibliotecas de estilos mais famosa, Bootstrap, é um exemplo perfeito de um framework irritante de se trabalhar. Considerando tudo que falamos até aqui, principalmente sobre o escopo único global para nomenclatura, e colisões entre nomes sendo uma péssima idéia, com Bootstrap nós temos:

  • Exportação de uma tonelada de seletores (na versão 3.3.7, são 2481 para ser mais exato) no escopo global, você usando eles ou não. (Um caso curioso, o navegador IE até a versão 9, consegue processar apenas 4095 seletores antes de, silenciosamente, ignorar o resto. Já fiquei sabendo de pessoas gastando dias debugando isso e se perguntando o que estava acontecendo).
  • Usar nome de classes como .btn.table. Não consigo imaginar que outro desenvolvedor irá usar os mesmos nomes em suas aplicações 🤓

Independentemente disso, vamos dizer que queremos usar Bootstrap como base para o nosso componente Button.

Ao invés de integrar pelo lado do HTML:

<button class="myapp-Button btn">

Considere extender os estilos dessa classe:

<button class="myapp-Button">
.myapp-Button {
// usando o Bootstrap
@extend .btn;
}

Isso reforça a idéia para todo mundo (inclusive você mesmo) de que os detalhes de implementação do <button> não precisam ser mostradas no HTML do componente. Uma consequência boa disso, é que se você decidir trocar o Bootstrap por qualquer outro framework (ou simplesmente escrever o seu estilo próprio), a mudança ocorrerá apenas em uma parte.

O mesmo princípio se aplica para as classes utilitárias, tento até a opção de escolher um nome mais apropriado para sua aplicação:

.myapp-Button {
// defino em outra parte do projeto
@extend .myapp-utils-button;
}

Ou, podemos evitar a criação da classe e usar um seletor de placeholder (ele é suportado pela maioria dos pré-processadores):

.myapp-Button {
// defino em outra parte do projeto
@extend %myapp-utils-button;
}

Falando nisso, todos os pré-processadores de CSS utilizam o conceito de mixins:

.myapp-Button {
@include myapp-generateCoolButton($padding: 15px, $withExplosions: true);
}

Vale lembra que, se você utilizar outro framework mais civilizado (como Bourbon ou Foundation), na verdade, eles estão fazendo o seguinte: declarando um monte de mixins para você usar como bem entender, e não criam estilos próprios. De bom gosto!.


Finalizando

Entenda as regras para saber quando quebrá-las

Como mencionado anteriormente, quando você entender as regras que você mesmo definiu (ou adotou de um estranho da interwebs), você pode criar exceções que façam sentido para você. Um exemplo, se você acha que vale a pena usar uma classe utilitária diretamente no HTML, você pode:

<button class="myapp-Button myapp-utils-button">

Como saber se vale a pena? Um exemplo, seu framework de testes automatizados pode usar essa classe para saber se o botão é ou não clicável.

Ou, você simplesmente definiu que é OK quebrar o encapsulamento do componente, apenas quando a brecha é pequena.

Mas é aí que eu vou lembrar você que, essas pequenas inconsistências podem sair fora de controle, lembre-se, consistência vale seu peso em ouro! Porém, se você e sua equipe estão de acordo, só vai!


Gostou do artigo? Manda um like ou um tweet. Aliás, não manda nada e só comenta embaixo, ou nem isso! (╯°□°)╯︵ ┻━┻…