DEV Community

Cover image for Use variáveis CSS como uma pessoa sênior com essas 5 dicas 🔥
Camilo Micheletto
Camilo Micheletto

Posted on

Use variáveis CSS como uma pessoa sênior com essas 5 dicas 🔥

Sumário


101 🔗

Repetição é bem comum no CSS, ainda mais em fontes, cores e espaçamentos. Repetição nem sempre é desperdício, se a gente quer ser consistente com o nosso design, repetição favorece a percepção de padronização das pessoas usuárias. No caso abaixo, temos um timing de transição padrão pras animações dos componentes:

.btn {
  transition: background 200ms ease-in;
}

.link {
  transition: color 200ms ease-in;
}
Enter fullscreen mode Exit fullscreen mode

 

Pra garantir a acessibilidade, temos que possibilitar que usuários que se distraem com animações ou possuem algum tipo de sensibilidade à movimento possam desativá-los via configuração do user-agent, que no caso é o navegador:

.btn {
  transition: background 200ms ease-in;
}

@media (prefers-reduced-motion: reduce) {
  .btn {
    transition: background linear;
  }
}

Enter fullscreen mode Exit fullscreen mode

 

Nesse caso, pra cada classe que tivesse esse timing teríamos que criar também dentro da @media pra que a pessoa usuária conseguisse mudar. E se além disso mudarmos o tema pra escuro? Teríamos que colocar todas as classes com cores dentro da @media (prefers-color-scheme: dark)?

Com variáveis CSS poderíamos fazer diferente!
Primeiro guardamos a nossa configuração de timing e easing num local que ela ficasse disponível "globalmente". Geralmente usamos :root {} que é um equivalente à html {}, mas que possuí ainda mais precedência.

:root {
  --timing: 200ms ease-in;
}
Enter fullscreen mode Exit fullscreen mode

 

E ai podemos atualizar todas referências desse valor no código usando a função var()

.btn {
  transition: background var(--timing);
}

.link {
  transition: color var(--timing);
}
Enter fullscreen mode Exit fullscreen mode

 

Só isso já nos daria a vantagem de, se caso for necessária a mudança de valor dessa propriedade, mudarmos em um lugar só, mas o pulo do gato não chegou ainda, se liga:

:root {
  --timing: 200ms ease-in;
  @media (prefers-reduced-motion: reduce) {
    --timing: linear;
  }
}
Enter fullscreen mode Exit fullscreen mode

Se a media condition (prefers-reduced-motion: reduce) retornar true, como a variável --timing: linear foi redeclarada depois, ela vai sobrescrever a declaração --timing: 200ms ease-in;. Percebe que não só economizamos linhas de CSS com isso, mas colocamos dentro do mesmo bloco toda a lógica desse comportamento?


Design tokens 🔗

Design tokens são os átomos que compõe os componentes de uma aplicação. Um botão por exemplo é feito com um tamanho de fonte específica, cores próprias e que reagem às mudanças de estado, bordas que seguem esses padrões de cores, um padding padrão nas bordas e um gap representando um gutter em botões que também tem ícones. Todas essas configurações podem ser consistentes em outros componentes e podem ser pré-estabelecidas e guardadas em variáveis - tokens.

 

Um exemplo do uso de tokens é a criação de um padrão de cores a ser usada numa aplicação. Usando variáveis CSS além de defini-las com um nome apropriado, deixando elas fáceis de reproduzir, mudar e manter, podemos altera elas facilmente em diferentes condições, como a mudança pra um tema escuro.

Abaixo um exemplo do framework de variáveis CSS OpenProps usando variáveis CSS e media features pra implementar um modo escuro.

Documentação da lib Open Props na seção Theming 101

 

No exemplo acima foi colocado o seletor :root dentro de uma media query com prefers-color-scheme: dark. Com isso, se a media condition for true, o :root contendo as variáveis com o tema escuro irá sobrescrever o anterior contendo o tema claro padrão.

Podemos criar múltiplos temas em componentes usando o mesmo sistema:


.button {
  color: var(--btn-text, #1A1A1A);
  background: var(--btn-bg, #FAFAFA);
}

.button[danger] {
  --btn-text: #FAFAFA;
  --btn-bg: #FF0000;
}

Enter fullscreen mode Exit fullscreen mode

 

No HTML:

<div>
  <button class="button">Tema claro</button>
  <button class="button" danger>Tema vermelho</button>
</div>
Enter fullscreen mode Exit fullscreen mode

 

Mas e se eu quisesse alterar um tema? Pra um elemento como um alert ou callout seria interessante adicionar um pouco de opacidade à cor de fundo, mas só isso. Como você faria?

TailwindCSS resolveu isso com composição de variáveis.


Composição de variáveis 🔗

No artigo Composing the Uncomposable with CSS Variables de Adam Wathan, o criador do TailwindCSS ele aborda justamente essa questão.

TailwindCSS é um plugin de PostCSS que gera classes utilitárias on-demand à partir de um Design System. Possuindo um DS, ele também possui os tokens que eu estava falando no tópico anterior:

Print do site do Tailwind com os tokens de imagem

 

Vamos supor que você aplica a classe .text-blue-300 que deixa a cor do texto num tom de azul e, ao mesmo tempo, aplica a classe .text-opacity-50 que deixa a cor do texto com opacidade de 50%.

A solução de Watham foi criar uma função de cor e no argumento de opacidade incluir uma variável CSS ao invés de um valor. Dessa forma, se .text-opacity- tivesse no mesmo escopo que uma classe de cor, ela "injetaria" a variável que ela contém na classe.

Abaixo, o exemplo do artigo citado acima em que a classe de opacidade "injeta" o valor de --text-opacity quando usada em um elemento junto com a classe de cor:

.text-blue-300 {
  --text-opacity: 1;
  color: rgba(144, 205, 244, var(--text-opacity));
}

.text-opacity-50 {
  --text-opacity: 0.5;
}
Enter fullscreen mode Exit fullscreen mode

 

Classes aplicadas ao mesmo elemento estão sob o mesmo escopo, ou seja, elas podem influenciar umas as outras através de variáveis CSS:

Exemplo visual de como variáveis podem influenciar classes diferentes dentro do mesmo escopo

 

Dito isso, será que é possível criar diversas variantes de um componente apenas injetando variáveis neles com classes modificadoras?

É. Bootstrap 5 fez isso com maestria.


Blocos enxutos 🔗

O componente de botão do Bootstrap possui 9 variantes, sendo uma delas um link, como eles fazem?

Print da documentação que mostra as 9 variantes de botões do bootstrap com código fonte

 

Esse é o código fonte do bootstrap/dist/css/bootstrap.css. Algumas propriedades foram omitidas por brevidade:

.btn {
  --bs-btn-padding-x: 0.75rem;
  --bs-btn-padding-y: 0.375rem;
  --bs-btn-font-family: ;
  --bs-btn-font-size: 1rem;
  --bs-btn-font-weight: 400;
  --bs-btn-line-height: 1.5;
  --bs-btn-color: var(--bs-body-color);
  --bs-btn-bg: transparent;
  --bs-btn-border-width: var(--bs-border-width);
  --bs-btn-border-color: transparent;
  --bs-btn-border-radius: var(--bs-border-radius);
  --bs-btn-hover-border-color: transparent;
  --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
  --bs-btn-disabled-opacity: 0.65;
  --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);

  display: inline-block;
  padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x);
  font-family: var(--bs-btn-font-family);
  font-size: var(--bs-btn-font-size);
  font-weight: var(--bs-btn-font-weight);
  line-height: var(--bs-btn-line-height);
  color: var(--bs-btn-color);
  border: var(--bs-btn-border-width) solid var(--bs-btn-border-color);
  border-radius: var(--bs-btn-border-radius);
  background-color: var(--bs-btn-bg)
}
Enter fullscreen mode Exit fullscreen mode

 

No código acima, percebemos que:

  • São definidos valores padrão dentro do escopo do componente, inclusive valores que referenciam outras variáveis, dessa forma evitando a repetição de tokens com diferentes nomenclaturas, possibilitando uma referência cruzada.

  • Embaixo são definidas as propriedades consumindo desses tokens.

Agora vamos ver a composição da variante .btn-primary:

.btn-primary {
  --bs-btn-color: #fff;
  --bs-btn-bg: #0d6efd;
  --bs-btn-border-color: #0d6efd;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #0b5ed7;
  --bs-btn-hover-border-color: #0a58ca;
  --bs-btn-focus-shadow-rgb: 49, 132, 253;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #0a58ca;
  --bs-btn-active-border-color: #0a53be;
  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
  --bs-btn-disabled-color: #fff;
  --bs-btn-disabled-bg: #0d6efd;
  --bs-btn-disabled-border-color: #0d6efd;
}
Enter fullscreen mode Exit fullscreen mode

 

Só tem variáveis CSS.

Através da especificidade a variante altera os valores sem adicionar ou redeclarar nenhuma propriedade, de forma extremamente enxuta (DRY).

Agora, o que você faria se precisasse entregar um layout como o abaixo?

10 cards enfileirados em duas linhas contendo 5 cards cada. Cada card tem uma cor distinta.

 

Usando essa mesma estratégia, criamos uma classe .card em que background-color recebe uma variável e pra cada nth-child(n)a gente muda a cor da variável:

.card {
  height: var(--size); width: var(--size);
  background-color: var(--color, gray);
  border-radius: 8px;
}

.card:nth-child(1) { --color: maroon; }
.card:nth-child(2) { --color: red; }
.card:nth-child(3) { --color: purple; }
.card:nth-child(4) { --color: fuchsia; }
.card:nth-child(5) { --color: green; }
.card:nth-child(6) { --color: lime; }
.card:nth-child(7) { --color: yellow; }
.card:nth-child(8) { --color: navy; }
.card:nth-child(9) { --color: blue; }
.card:nth-child(10) { --color: aqua; }
Enter fullscreen mode Exit fullscreen mode

 

Mas se forem 20 cards, 30 cards? O CSS vai ficar enorme né? Se pelo menos tivesse como a gente passar as cores pelo elemento, como as props no React.

😈 e se eu te disser que tem como?


Variáveis como props 🔗

Diferente das outras estratégias, essa começa na marcação. Mantemos o código da variável .card, mas mudamos como inserimos o valor da cor:


<li class="card" style="--color: tomato"></li>

Enter fullscreen mode Exit fullscreen mode

 

CSS inline. Podem rir de mim, fãs de Tailwind.

 

O conceito "parece" ruim, mas não é. Primeiro que diferente de inserir declarações de CSS (propriedade e valor), sobrescrevendo o CSS local, não sobrescrevemos ou adicionamos nada no CSS do elemento, apenas valores. Dessa forma mantemos a mesma integridade do CSS da folha de estilos, ainda com um ganho de especificidade da declaração inline.

Outro ponto é que em linguagens de templating como JSX ou afins, podemos literalmente alterar os estilos de forma lógica sem, muitas vezes, a necessidade de uma solução CSS-in-JS:

Qual a diferença disso...

<Contact color="darkgreen" />
Enter fullscreen mode Exit fullscreen mode

 

Pra isso...

<div class="contact" style="--color: darkgreen">
  <!-- ... -->
</div>
Enter fullscreen mode Exit fullscreen mode

 

Ou até mesmo isso...?

<div class="contact" style={{ '--color': contact.color }}>
  <!-- ... -->
</div>
Enter fullscreen mode Exit fullscreen mode

 

Off-topic → É possível usar HTML da mesma forma que usamos props em Styled Components

 

O que te impede de fazer isso?

<button variant="primary" dark>Follow for more!</button>
Enter fullscreen mode Exit fullscreen mode

 

E isso aqui?

button[variant=primary] {
   background-color: var(--background, red);
   color: var(--background, black);

   &[dark] {
      --background: darkred;
      --color: white;
   }
}
Enter fullscreen mode Exit fullscreen mode
  • Não use se você precisar ter um código W3C compliant
  • '&' é CSS válido, viu?

 

Vamos supor que ao invés de um botão completamente limpo quiséssemos que a classe base .btn começasse com estilos padrão. Pra isso, podemos usar o segundo parâmetro da função var(), que serve como fallback caso a variável inicial não esteja declarada.

O problema dessa abordagem é que em variantes e outros estados precisamos sempre preencher com um valor padrão caso --background ou --color nunca sejam declarados:

.btn {
   background: var(--background, #FAFAFA);
   color: var(--color, #1A1A1A);
}

.btn:focus {
  outline: 1px solid var(--background, #FAFAFA);
}
Enter fullscreen mode Exit fullscreen mode

 

Existe forma melhor de fazer isso? A membro da W3C e convidada do CSS Working Group Lea Verou veio com uma abordagem muito boa sobre.


Variáveis privadas 🔗

Algumas alternativas pro problema de redeclaração de parâmetros de fallback em variáveis CSS seriam:

1 - Declaração de fallback no escopo do componente

.btn {
   --background: #FAFAFA;
   --color: #1A1A1A;
   background: var(--background);
   color: var(--color);
}

.btn:focus {
   --background: #FAFAFA;
  outline: 1px solid var(--background);
}
Enter fullscreen mode Exit fullscreen mode

Funcional, porém impede o componente de herdar --colorde um elemento pai, somente através de especificidade, o que pode se tornar difícil de gerenciar pra seletores muito complexos.

 

2 - Criação de uma variável de fallback usando o parâmetro default do var()

.btn {
   --background-initial: #FAFAFA;
   --color-initial: #1A1A1A;
   background: var(--background, var(--background-initial));
   color: var(--color, var(--color-initial));
}

.btn:focus {
   --background-initial: #FAFAFA;
  outline: 1px solid var(--background, var(--background-initial));
}
Enter fullscreen mode Exit fullscreen mode

Nessa implementação conseguimos herdar a propriedade --background e --color do elemento pai, mesmo sem uma especificidade superior, porém é bem verbosa né?

A Lea Verou cunhou o conceito de pseudo private custom properties, que funcionam similarmente à variáveis privadas em linguagens de programação (vide o sublinhado --_ no nome.

Nesse conceito criamos uma variável de fallback como anteriormente, mas diferente dessa ela terá o próprio valor padrão:

.btn {
   --_background: var(--background, #FAFAFA);
   --_color: var(--color, #1A1A1A);
   background: var(--background);
   color: var(--color);
}

.btn:focus {
  outline: 1px solid var(--_background);
}
Enter fullscreen mode Exit fullscreen mode

Dessa forma conseguimos:

  1. Herdar as propriedades --background e --color.
  2. Definir um fallback pra cada propriedade.
  3. Fazê-lo de forma muito menos verbosa.

 


 

Todas essas estratégias me ajudaram a pensar e escrever CSS de forma muito mais enxuta e criativa, permitindo com que eu criasse layouts complexos com uma carga cognitiva menor e de forma mais extensível e customizável.

Um exemplo é esse bento layout responsivo com apenas 43 linhas de CSS:

 

Não sabe o que é um bento layout? Tá na mão.


Materiais de estudo 🔗

Top comments (8)

Collapse
 
raulferreirasilva profile image
Raul Ferreira

Muito obrigado, sempre me perco com meus CSSs, por repetição de código e estava em busca de melhorar, achar alguma maneira de otimizar tudo e lendo esse artigo me abriu a mente e me deu um norte do que e de como procurar.

Muitas crenças que eu tinha sobre css estão mudando por sua culpa, a mais importante foi a de responsividade, sempre me enrolava todo linhas e linhas da mesma coisa pra arrumar em uma tela especifica, temidas medias queries, agora busco utilizar grid layout e evoluir aos poucos e seus ensinamentos e códigos fornecidos nos artigos.

Ainda tem muita coisa que me perco e que preciso reler umas 2 vezes e ainda fico sem entender e não sei nem como fazer uma pergunta sobre o assunto, mas é muito legal ter contato com códigos de uma senioridade maior que a sua.

obrigaduu por esta me ajudando evoluir 🦤.

Collapse
 
lixeletto profile image
Camilo Micheletto

Manoooo, sempre que quiser, aqui, no twitter ou no LinkedIn, pode perguntar. Não existe pergunta ruim, visse?

Collapse
 
laerciolopesll profile image
LaercioLopesLL

Muito bom, parabéns!

Collapse
 
jessilyneh profile image
Jessilyneh

aaaah, eu adoro seus conteudos, obrigada por mais esse texto incrivel!!!

Collapse
 
lixeletto profile image
Camilo Micheletto

Eu adorooooo que vc gosta, te admiro muito, é sempre babadeiro vc gostar do meu trampo

Collapse
 
ofelipexis profile image
Felipe Mendes

post sensacional!

Collapse
 
lixeletto profile image
Camilo Micheletto

Valeu manoooo

Collapse
 
kvnol profile image
Kevin Oliveira

Sensacional, mano!!! Variáveis CSS são lindas.