Sumário
- 101
- Design tokens
- Composição de variáveis
- Blocos enxutos
- Variáveis como props
- Variáveis privadas
- Materiais de estudo
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;
}
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;
}
}
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;
}
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);
}
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;
}
}
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.
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;
}
No HTML:
<div>
<button class="button">Tema claro</button>
<button class="button" danger>Tema vermelho</button>
</div>
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:
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;
}
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:
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?
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)
}
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;
}
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?
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; }
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>
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" />
Pra isso...
<div class="contact" style="--color: darkgreen">
<!-- ... -->
</div>
Ou até mesmo isso...?
<div class="contact" style={{ '--color': contact.color }}>
<!-- ... -->
</div>
O que te impede de fazer isso? E isso aqui?Off-topic → É possível usar HTML da mesma forma que usamos props em Styled Components
<button variant="primary" dark>Follow for more!</button>
button[variant=primary] {
background-color: var(--background, red);
color: var(--background, black);
&[dark] {
--background: darkred;
--color: white;
}
}
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);
}
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);
}
Funcional, porém impede o componente de herdar --color
de 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));
}
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);
}
Dessa forma conseguimos:
- Herdar as propriedades
--background
e--color
. - Definir um fallback pra cada propriedade.
- 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.
![lixeletto](https://res.cloudinary.com/practicaldev/image/fetch/s--Aje0MoRH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/practicaldev/image/fetch/s--c0FqtH2n--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/393949/bdd2b0c8-ca93-4c85-9f92-4f62fe8fcb92.jpg)
Bento Grid layout responsivo em 40 linhas de CSS🥢 🍱
Camilo Micheletto ・ Nov 17
Materiais de estudo 🔗
Composing the Uncomposable With CSS Variables - Adam Watham - artigo, em inglês
Custom properties with defaults - Lea Verou - artigo, em inglês
Using CSS custom properties like this is a waste - Kevin Powell - vídeo, em inglês
Bootstrap custom properites - Bootstrap Documentation - artigo, em inglês
Getting started with CSS Custom Properties - Andy Bell - artigo, em inglês
Variáveis CSS, um guia prático - Tárcio Zemel - artigo, em português
Top comments (8)
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 🦤.
Manoooo, sempre que quiser, aqui, no twitter ou no LinkedIn, pode perguntar. Não existe pergunta ruim, visse?
Muito bom, parabéns!
aaaah, eu adoro seus conteudos, obrigada por mais esse texto incrivel!!!
Eu adorooooo que vc gosta, te admiro muito, é sempre babadeiro vc gostar do meu trampo
post sensacional!
Valeu manoooo
Sensacional, mano!!! Variáveis CSS são lindas.