Um guia de estratégia para propriedades personalizadas de CSS
Publicados: 2022-03-10Propriedades personalizadas de CSS (às vezes conhecidas como 'variáveis CSS') agora são suportadas em todos os navegadores modernos, e as pessoas estão começando a usá-las na produção. Isso é ótimo, mas são diferentes de variáveis em pré-processadores, e já vi muitos exemplos de pessoas usando-as sem considerar as vantagens que elas oferecem.
As propriedades personalizadas têm um enorme potencial para mudar a forma como escrevemos e estruturamos o CSS e, em menor grau, como usamos o JavaScript para interagir com os componentes da interface do usuário. Eu não vou focar na sintaxe e como eles funcionam (para isso eu recomendo que você leia “É hora de começar a usar propriedades personalizadas”). Em vez disso, quero dar uma olhada mais profunda nas estratégias para aproveitar ao máximo as propriedades personalizadas do CSS.
Como elas são semelhantes às variáveis em pré-processadores?
Propriedades personalizadas são um pouco como variáveis em pré-processadores, mas têm algumas diferenças importantes. A primeira e mais óbvia diferença é a sintaxe.
Com SCSS
, usamos um símbolo de dólar para denotar uma variável:
$smashing-red: #d33a2c;
Em Less usamos um símbolo @
:
@smashing-red: #d33a2c;
As propriedades personalizadas seguem convenções semelhantes e usam um prefixo --
:
:root { --smashing-red: #d33a2c; } .smashing-text { color: var(--smashing-red); }
Uma diferença importante entre propriedades personalizadas e variáveis em pré-processadores é que as propriedades personalizadas têm uma sintaxe diferente para atribuir um valor e recuperar esse valor. Ao recuperar o valor de uma propriedade personalizada, usamos a função var()
.
A próxima diferença mais óbvia está no nome. Eles são chamados de 'propriedades personalizadas' porque são realmente propriedades CSS. Em pré-processadores, você pode declarar e usar variáveis em praticamente qualquer lugar, incluindo blocos de declaração externos, em regras de mídia ou até mesmo como parte de um seletor.
$breakpoint: 800px; $smashing-red: #d33a2c; $smashing-things: ".smashing-text, .cats"; @media screen and (min-width: $breakpoint) { #{$smashing-things} { color: $smashing-red; } }
A maioria dos exemplos acima seria inválida usando propriedades personalizadas.
As propriedades personalizadas têm as mesmas regras sobre onde podem ser usadas como propriedades CSS normais. É muito melhor pensar neles como propriedades dinâmicas do que como variáveis. Isso significa que eles só podem ser usados dentro de um bloco de declaração, ou em outras palavras, as propriedades personalizadas estão vinculadas a um seletor. Este pode ser o seletor :root
ou qualquer outro seletor válido.
:root { --smashing-red: #d33a2c; } @media screen and (min-width: 800px) { .smashing-text, .cats { --margin-left: 1em; } }
Você pode recuperar o valor de uma propriedade personalizada em qualquer lugar em que usaria um valor em uma declaração de propriedade. Isso significa que eles podem ser usados como um único valor, como parte de uma instrução abreviada ou até mesmo dentro de equações calc()
.
.smashing-text, .cats { color: var(--smashing-red); margin: 0 var(--margin-horizontal); padding: calc(var(--margin-horizontal) / 2) }
No entanto, eles não podem ser usados em consultas de mídia ou seletores incluindo :nth-child()
.
Provavelmente há muito mais que você deseja saber sobre a sintaxe e como as propriedades personalizadas funcionam, como como usar valores de fallback e você pode atribuir variáveis a outras variáveis (sim), mas esta introdução básica deve ser suficiente para entender o restante os conceitos deste artigo. Para obter mais informações sobre as especificidades de como as propriedades personalizadas funcionam, você pode ler “É hora de começar a usar as propriedades personalizadas” escrito por Serg Hospodarets.
Dinâmico vs. Estático
Diferenças cosméticas à parte, a diferença mais significativa entre variáveis em pré-processadores e propriedades personalizadas é como elas são definidas. Podemos nos referir a variáveis como escopo estático ou dinâmico. As variáveis nos pré-processadores são estáticas, enquanto as propriedades personalizadas são dinâmicas.
No que diz respeito ao CSS, estático significa que você pode atualizar o valor de uma variável em diferentes pontos do processo de compilação, mas isso não pode alterar o valor do código que veio antes dele.
$background: blue; .blue { background: $background; } $background: red; .red { background: $background; }
resulta em:
.blue { background: blue; } .red { background: red; }
Uma vez que isso é renderizado para CSS, as variáveis desaparecem. Isso significa que poderíamos ler um arquivo .scss
e determinar sua saída sem saber nada sobre o HTML, navegador ou outras entradas. Este não é o caso com propriedades personalizadas.
Os pré-processadores têm uma espécie de “escopo de bloco” onde as variáveis podem ser alteradas temporariamente dentro de um seletor, função ou mixin. Isso altera o valor de uma variável dentro do bloco, mas ainda é estático. Isso está vinculado ao bloco, não ao seletor. No exemplo abaixo, a variável $background
é alterada dentro do bloco .example
. Ele volta ao valor inicial fora do bloco, mesmo se usarmos o mesmo seletor.
$background: red; .example { $background: blue; background: $background; } .example { background: $background; }
Isso resultará em:
.example { background: blue; } .example { background: red; }
As propriedades personalizadas funcionam de forma diferente. No que diz respeito às propriedades personalizadas, o escopo dinâmico significa que elas estão sujeitas à herança e à cascata. A propriedade está vinculada a um seletor e, se o valor for alterado, isso afetará todos os elementos DOM correspondentes, assim como qualquer outra propriedade CSS.
Isso é ótimo porque você pode alterar o valor de uma propriedade personalizada dentro de uma consulta de mídia, com um pseudo seletor, como hover, ou até mesmo com JavaScript.
a { --link-color: black; } a:hover, a:focus { --link-color: tomato; } @media screen and (min-width: 600px) { a { --link-color: blue; } } a { color: var(--link-color); }
Não precisamos alterar onde a propriedade personalizada é usada — alteramos o valor da propriedade personalizada com CSS. Isso significa usar a mesma propriedade customizada, podemos ter valores diferentes em lugares ou contextos diferentes na mesma página.
Global vs. Local
Além de serem estáticas ou dinâmicas, as variáveis também podem ser globais ou locais. Se você escreve JavaScript, você estará familiarizado com isso. As variáveis podem ser aplicadas a tudo dentro de um aplicativo ou seu escopo pode ser limitado a funções específicas ou blocos de código.
CSS é semelhante. Temos algumas coisas que são aplicadas globalmente e outras que são mais locais. Cores da marca, espaçamento vertical e tipografia são exemplos de coisas que você pode querer que sejam aplicadas global e consistentemente em seu site ou aplicativo. Também temos coisas locais. Por exemplo, um componente de botão pode ter uma variante pequena e grande. Você não gostaria que os tamanhos desses botões fossem aplicados a todos os elementos de entrada ou mesmo a todos os elementos da página.
Isso é algo com o qual estamos familiarizados em CSS. Desenvolvemos sistemas de design, convenções de nomenclatura e bibliotecas JavaScript, tudo para ajudar a isolar componentes locais e elementos de design globais. As propriedades personalizadas fornecem novas opções para lidar com esse problema antigo.
As propriedades personalizadas de CSS são, por padrão, com escopo local para os seletores específicos aos quais as aplicamos. Então eles são como variáveis locais. No entanto, as propriedades personalizadas também são herdadas, portanto, em muitas situações, elas se comportam como variáveis globais — especialmente quando aplicadas ao seletor :root
. Isso significa que precisamos ser cuidadosos sobre como usá-los.
Muitos exemplos mostram propriedades personalizadas sendo aplicadas ao elemento :root
e, embora isso seja bom para uma demonstração, pode resultar em um escopo global confuso e problemas não intencionais com herança. Felizmente, já aprendemos essas lições.
Variáveis globais tendem a ser estáticas
Existem algumas pequenas exceções, mas de um modo geral, a maioria das coisas globais em CSS também são estáticas.
Variáveis globais como cores da marca, tipografia e espaçamento não tendem a mudar muito de um componente para o outro. Quando eles mudam, isso tende a ser um rebranding global ou alguma outra mudança significativa que raramente acontece em um produto maduro. Ainda faz sentido que essas coisas sejam variáveis, elas são usadas em muitos lugares e as variáveis ajudam na consistência. Mas não faz sentido que eles sejam dinâmicos. O valor dessas variáveis não muda de forma dinâmica.
Por esse motivo, recomendo fortemente o uso de pré-processadores para variáveis globais (estáticas). Isso não apenas garante que eles sejam sempre estáticos, mas também os denota visualmente dentro do código. Isso pode tornar o CSS muito mais legível e fácil de manter.
Variáveis estáticas locais estão OK (às vezes)
Você pode pensar, dada a forte postura sobre variáveis globais serem estáticas, que por reflexão, todas as variáveis locais podem precisar ser dinâmicas. Embora seja verdade que as variáveis locais tendem a ser dinâmicas, isso não é nem de longe tão forte quanto a tendência de uma variável global ser estática.
Variáveis localmente estáticas são perfeitamente aceitáveis em muitas situações. Eu uso variáveis de pré-processadores em arquivos de componentes principalmente para conveniência do desenvolvedor.
Considere o exemplo clássico de um componente de botão com várias variações de tamanho.
Meu scss
pode ser algo assim:
$button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { // Visual styles } .btn-sml { font-size: $button-sml; } .btn-med { font-size: $button-med; } .btn-lrg { font-size: $button-lrg; }
Obviamente, este exemplo faria mais sentido se eu estivesse usando as variáveis várias vezes ou derivando valores de margem e preenchimento das variáveis de tamanho. No entanto, a capacidade de prototipar rapidamente diferentes tamanhos pode ser um motivo suficiente.
Como a maioria das variáveis estáticas são globais, gosto de diferenciar variáveis estáticas que são usadas apenas dentro de um componente. Para fazer isso, você pode prefixar essas variáveis com o nome do componente ou pode usar outro prefixo, como c-variable-name
para component ou l-variable-name
para local. Você pode usar o prefixo que quiser ou pode prefixar variáveis globais. Seja qual for a sua escolha, é útil diferenciar, especialmente ao converter uma base de código existente para usar propriedades personalizadas.
Quando usar propriedades personalizadas
Se não há problema em usar variáveis estáticas dentro de componentes, quando devemos usar propriedades personalizadas? A conversão de variáveis de pré-processador existentes em propriedades personalizadas geralmente faz pouco sentido. Afinal, o motivo das propriedades personalizadas é completamente diferente. Propriedades personalizadas fazem sentido quando temos propriedades CSS que mudam em relação a uma condição no DOM — especialmente uma condição dinâmica como :focus
, :hover
, media queries ou com JavaScript.
Suspeito que sempre usaremos alguma forma de variáveis estáticas, embora possamos precisar de menos no futuro, pois as propriedades personalizadas oferecem novas maneiras de organizar a lógica e o código. Até lá, acho que na maioria das situações trabalharemos com uma combinação de variáveis de pré-processador e propriedades personalizadas.
É útil saber que podemos atribuir variáveis estáticas a propriedades personalizadas. Sejam elas globais ou locais, faz sentido em muitas situações converter variáveis estáticas em propriedades personalizadas dinâmicas localmente.
Observação : você sabia que $var
é um valor válido para uma propriedade personalizada? Versões recentes do Sass reconhecem isso e, portanto, precisamos interpolar variáveis atribuídas a propriedades personalizadas, como esta: #{$var}
. Isso diz ao Sass que você deseja gerar o valor da variável, em vez de apenas $var
na folha de estilo. Isso é necessário apenas para situações como propriedades personalizadas, onde nomes de variáveis também podem ser um CSS válido.
Se pegarmos o exemplo do botão acima e decidirmos que todos os botões devem usar a pequena variação em dispositivos móveis, independentemente da classe aplicada no HTML, esta é agora uma situação mais dinâmica. Para isso, devemos usar propriedades personalizadas.
$button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { --button-size: #{$button-sml}; } @media screen and (min-width: 600px) { .btn-med { --button-size: #{$button-med}; } .btn-lrg { --button-size: #{$button-lrg}; } } .btn { font-size: var(--button-size); }
Aqui eu crio uma única propriedade personalizada: --button-size
. Essa propriedade customizada é inicialmente delimitada para todos os elementos de botão usando a classe btn
. Em seguida, altero o valor de --button-size
acima de 600px para as classes btn-med
e btn-lrg
. Por fim, aplico essa propriedade personalizada a todos os elementos de botão em um só lugar.
Não seja muito esperto
A natureza dinâmica das propriedades personalizadas nos permite criar alguns componentes inteligentes e complicados.
Com a introdução de pré-processadores, muitos de nós criamos bibliotecas com abstrações inteligentes usando mixins e funções personalizadas. Em casos limitados, exemplos como este ainda são úteis hoje, mas na maioria das vezes, quanto mais tempo eu trabalho com pré-processadores, menos recursos eu uso. Hoje, uso pré-processadores quase exclusivamente para variáveis estáticas.
As propriedades personalizadas não serão (e não devem) ser imunes a esse tipo de experimentação, e estou ansioso para ver muitos exemplos inteligentes. Mas, a longo prazo, código legível e sustentável sempre vencerá abstrações inteligentes (pelo menos em produção).
Eu li um excelente artigo sobre este tópico no Free Code Camp Medium recentemente. Foi escrito por Bill Sourour e se chama “Don't Do It At Runtime. Faça isso na hora do design.” Em vez de parafrasear seus argumentos, vou deixar você ler.
Uma diferença importante entre as variáveis do pré-processador e as propriedades personalizadas é que as propriedades personalizadas funcionam em tempo de execução. Isso significa que coisas que podem ter sido aceitáveis no limite, em termos de complexidade, com pré-processadores podem não ser uma boa ideia com propriedades personalizadas.
Um exemplo que ilustrou isso para mim recentemente foi este:
:root { --font-scale: 1.2; --font-size-1: calc(var(--font-scale) * var(--font-size-2)); --font-size-2: calc(var(--font-scale) * var(--font-size-3)); --font-size-3: calc(var(--font-scale) * var(--font-size-4)); --font-size-4: 1rem; }
Isso gera uma escala modular. Uma escala modular é uma série de números que se relacionam entre si usando uma proporção. Eles são frequentemente usados em web design e desenvolvimento para definir tamanhos de fonte ou espaçamento.
Neste exemplo, cada propriedade personalizada é determinada usando calc()
, pegando o valor da propriedade personalizada anterior e multiplicando isso pela proporção. Fazendo isso, podemos obter o próximo número na escala.
Isso significa que as proporções são calculadas em tempo de execução e você pode alterá-las atualizando apenas o valor da propriedade --font-scale
. Por exemplo:
@media screen and (min-width: 800px) { :root { --font-scale: 1.33; } }
Isso é inteligente, conciso e muito mais rápido do que calcular todos os valores novamente, caso você queira alterar a escala. Também é algo que eu não faria no código de produção.
Embora o exemplo acima seja útil para prototipagem, em produção, prefiro ver algo assim:
:root { --font-size-1: 1.728rem; --font-size-2: 1.44rem; --font-size-3: 1.2em; --font-size-4: 1em; } @media screen and (min-width: 800px) { :root { --font-size-1: 2.369rem; --font-size-2: 1.777rem; --font-size-3: 1.333rem; --font-size-4: 1rem; } }
Semelhante ao exemplo no artigo de Bill, acho útil ver quais são os valores reais. Lemos o código muito mais vezes do que o escrevemos e valores globais, como escalas de fonte, mudam com pouca frequência na produção.
O exemplo acima ainda não é perfeito. Ele viola a regra anterior de que os valores globais devem ser estáticos . Eu prefiro usar variáveis de pré-processador e convertê-las em propriedades personalizadas localmente dinâmicas usando as técnicas demonstradas anteriormente.
Também é importante evitar situações em que passamos do uso de uma propriedade personalizada para uma propriedade personalizada diferente. Isso pode acontecer quando nomeamos propriedades assim.
Altere o valor e não a variável
Alterar o valor e não a variável é uma das estratégias mais importantes para usar as propriedades personalizadas de forma eficaz.
Como regra geral, você nunca deve alterar qual propriedade personalizada é usada para uma única finalidade. É fácil de fazer porque é exatamente assim que fazemos as coisas com pré-processadores, mas faz pouco sentido com propriedades personalizadas.
Neste exemplo, temos duas propriedades personalizadas que são usadas em um componente de exemplo. Eu mudo de usar o valor de --font-size-small
para --font-size-large
dependendo do tamanho da tela.
:root { --font-size-small: 1.2em; --font-size-large: 2em; } .example { font-size: var(--font-size-small); } @media screen and (min-width: 800px) { .example { font-size: var(--font-size-large); } }
Uma maneira melhor de fazer isso seria definir uma única propriedade personalizada com escopo para o componente. Em seguida, usando uma consulta de mídia ou qualquer outro seletor, altere seu valor.
.example { --example-font-size: 1.2em; } @media screen and (min-width: 800px) { .example { --example-font-size: 2em; } }
Por fim, em um único lugar, utilizo o valor desta propriedade customizada:
.example { font-size: var(--example-font-size); }
Neste exemplo e em outros anteriores, as consultas de mídia foram usadas apenas para alterar o valor das propriedades personalizadas. Você também pode notar que há apenas um local onde a instrução var()
é usada e as propriedades CSS regulares são atualizadas.
Essa separação entre declarações de variáveis e declarações de propriedades é intencional. Há muitas razões para isso, mas os benefícios são mais óbvios quando se pensa em design responsivo.
Design responsivo com propriedades personalizadas
Uma das dificuldades com o design responsivo quando ele depende muito de consultas de mídia é que, não importa como você organize seu CSS, os estilos relacionados a um componente específico ficam fragmentados na folha de estilo.
Pode ser muito difícil saber quais propriedades CSS serão alteradas. Ainda assim, CSS Custom Properties pode nos ajudar a organizar parte da lógica relacionada ao design responsivo e facilitar muito o trabalho com media queries.
Se mudar, é uma variável
As propriedades que mudam usando consultas de mídia são inerentemente dinâmicas e as propriedades personalizadas fornecem os meios para expressar valores dinâmicos em CSS. Isso significa que, se você estiver usando uma consulta de mídia para alterar qualquer propriedade CSS, deverá colocar esse valor em uma propriedade personalizada.
Você pode então mover isso, junto com todas as regras de mídia, estados de foco ou qualquer seletor dinâmico que define como o valor muda, para a parte superior do documento.
Separar a lógica do design
Quando feita corretamente, a separação da lógica e do design significa que as consultas de mídia são usadas apenas para alterar o valor das propriedades personalizadas . Isso significa que toda a lógica relacionada ao design responsivo deve estar no topo do documento, e sempre que vemos uma instrução var()
em nosso CSS, sabemos imediatamente que essa propriedade muda. Com os métodos tradicionais de escrever CSS, não havia como saber isso de relance.
Muitos de nós ficamos muito bons em ler e interpretar CSS rapidamente enquanto rastreamos em nossa cabeça quais propriedades mudaram em diferentes situações. Estou cansado disso e não quero mais fazer isso! As propriedades personalizadas agora fornecem um link entre a lógica e sua implementação, portanto, não precisamos rastrear isso, e isso é incrivelmente útil!
A dobra lógica
A ideia de declarar variáveis no topo de um documento ou função não é nova. É algo que fazemos na maioria das linguagens, e agora também podemos fazer em CSS. Escrever CSS desta forma cria uma clara distinção visual entre CSS na parte superior do documento e abaixo. Eu preciso de uma maneira de diferenciar essas seções quando falo sobre elas e a ideia de “dobra lógica” é uma metáfora que comecei a usar.
Acima da dobra contém todas as variáveis de pré-processador e propriedades personalizadas. Isso inclui todos os diferentes valores que uma propriedade customizada pode ter. Deve ser fácil rastrear como uma propriedade personalizada é alterada.
CSS abaixo da dobra é direto e altamente declarativo e fácil de ler. Parece CSS antes das consultas de mídia e outras complexidades necessárias do CSS moderno.
Dê uma olhada em um exemplo realmente simples de um sistema de grade flexbox de seis colunas:
.row { --row-display: block; } @media screen and (min-width: 600px) { .row { --row-display: flex; } }
A propriedade customizada --row-display
é inicialmente definida como block
. Acima de 600px, o modo de exibição é definido como flexível.
Abaixo da dobra pode ficar assim:
.row { display: var(--row-display); flex-direction: row; flex-wrap: nowrap; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6 { flex-grow: 0; flex-shrink: 0; } .col-1 { flex-basis: 16.66%; } .col-2 { flex-basis: 33.33%; } .col-3 { flex-basis: 50%; } .col-4 { flex-basis: 66.66%; } .col-5 { flex-basis: 83.33%; } .col-6 { flex-basis: 100%; }
Sabemos imediatamente que --row-display
é um valor que muda. Inicialmente, será block
, então os valores flex serão ignorados.
Este exemplo é bastante simples, mas se o expandirmos para incluir uma coluna de largura flexível que preencha o espaço restante, é provável que os valores flex-grow
, flex-shrink
e flex-basis
precisem ser convertidos em propriedades personalizadas. Você pode tentar isso ou dar uma olhada em um exemplo mais detalhado aqui.
Propriedades personalizadas para temas
Na maioria das vezes, argumentei contra o uso de propriedades personalizadas para variáveis dinâmicas globais e, espero, sugeri que anexar propriedades personalizadas ao seletor :root
é, em muitos casos, considerado prejudicial. Mas toda regra tem uma exceção e, para propriedades personalizadas, é o tema.
O uso limitado de propriedades personalizadas globais pode facilitar muito a criação de temas.
O tema geralmente se refere a permitir que os usuários personalizem a interface do usuário de alguma forma. Isso pode ser algo como mudar as cores em uma página de perfil. Ou pode ser algo mais localizado. Por exemplo, você pode escolher a cor de uma nota no aplicativo Google Keep.
A criação de temas geralmente envolve a compilação de uma folha de estilo separada para substituir um valor padrão com as preferências do usuário ou a compilação de uma folha de estilo diferente para cada usuário. Ambos podem ser difíceis e ter um impacto no desempenho.
Com propriedades personalizadas, não precisamos compilar uma folha de estilo diferente; precisamos apenas atualizar o valor das propriedades de acordo com as preferências do usuário. Como são valores herdados, se fizermos isso no elemento raiz, eles poderão ser usados em qualquer lugar em nossa aplicação.
Capitalize Propriedades Dinâmicas Globais
As propriedades personalizadas diferenciam maiúsculas de minúsculas e, como a maioria das propriedades personalizadas será local, se você estiver usando propriedades dinâmicas globais, pode fazer sentido capitalizá-las.
:root { --THEME-COLOR: var(--user-theme-color, #d33a2c); }
A capitalização de variáveis geralmente significa constantes globais. Para nós, isso significará que a propriedade está definida em outro lugar no aplicativo e que provavelmente não devemos alterá-la localmente.
Evite definir diretamente as propriedades dinâmicas globais
As propriedades personalizadas aceitam um valor de fallback. Pode ser útil evitar sobrescrever diretamente o valor de propriedades personalizadas globais e manter os valores do usuário separados. Podemos usar o valor de fallback para fazer isso.
O exemplo acima define o valor de --THEME-COLOR
para o valor de --user-theme-color
se existir. Se --user-theme-color
não estiver definido, o valor de #d33a2c
será usado. Dessa forma, não precisamos fornecer um fallback toda vez que usarmos --THEME-COLOR
.
Você pode esperar no exemplo abaixo que o plano de fundo seja definido como green
. No entanto, o valor de --user-theme-color
não foi definido no elemento raiz, portanto, o valor de --THEME-COLOR
não foi alterado.
:root { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }
A configuração indireta de propriedades dinâmicas globais como essa as protege de serem substituídas localmente e garante que as configurações do usuário sejam sempre herdadas do elemento raiz. Esta é uma convenção útil para proteger seus valores de tema e evitar herança não intencional.
Se quisermos expor propriedades específicas à herança, podemos substituir o seletor :root
por um seletor *
:
* { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }
Agora o valor de --THEME-COLOR
é recalculado para cada elemento e, portanto, o valor local de --user-theme-color
pode ser usado. Em outras palavras, a cor de fundo neste exemplo será green
.
Você pode ver alguns exemplos mais detalhados desse padrão na seção Manipulando cores com propriedades personalizadas.
Atualizando propriedades personalizadas com JavaScript
Se você deseja definir propriedades personalizadas usando JavaScript, existe uma API bastante simples e se parece com isso:
const elm = document.documentElement; elm.style.setProperty('--USER-THEME-COLOR', 'tomato');
Aqui estou definindo o valor de --USER-THEME-COLOR
no elemento do documento, ou em outras palavras, o elemento :root
onde ele será herdado por todos os elementos.
Esta não é uma nova API; é o mesmo método JavaScript para atualizar estilos em um elemento. Estes são estilos inline, então eles terão uma especificidade maior do que o CSS regular.
Isso significa que é fácil aplicar personalizações locais:
.note { --note-color: #eaeaea; } .note { background: var(--note-color); }
Aqui eu defino um valor padrão para --note-color
e o escopo para o componente .note
. Eu mantenho a declaração de variável separada da declaração de propriedade, mesmo neste exemplo simples.
const elm = document.querySelector('#note-uid'); elm.style.setProperty('--note-color', 'yellow');
Em seguida, direciono uma instância específica de um elemento .note
e altero o valor da propriedade personalizada --note-color
somente para esse elemento. Isso agora terá maior especificidade do que o valor padrão.
Você pode ver como isso funciona com este exemplo usando React. Essas preferências do usuário podem ser salvas no armazenamento local ou, talvez no caso de um aplicativo maior, em um banco de dados.
Manipulando cores com propriedades personalizadas
Além de valores hexadecimais e cores nomeadas, o CSS possui funções de cores como rgb()
e hsl()
. Isso nos permite especificar componentes individuais de uma cor, como matiz ou luminosidade. Propriedades personalizadas podem ser usadas em conjunto com funções de cor.
:root { --hue: 25; } body { background: hsl(var(--hue), 80%, 50%); }
Isso é útil, mas alguns dos recursos mais usados dos pré-processadores são funções de cores avançadas que nos permitem manipular cores usando funções como clarear, escurecer ou dessaturar:
darken($base-color, 10%); lighten($base-color, 10%); desaturate($base-color, 20%);
Seria útil ter alguns desses recursos nos navegadores. Eles estão chegando, mas até que tenhamos funções nativas de modificação de cores em CSS, as propriedades personalizadas podem preencher parte dessa lacuna.
Vimos que as propriedades personalizadas podem ser usadas dentro de funções de cores existentes, como rgb()
e hsl()
, mas também podem ser usadas em calc()
. Isso significa que podemos converter um número real em porcentagem multiplicando-o, por exemplo, calc(50 * 1%)
= 50%
.
:root { --lightness: 50; } body { background: hsl(25, 80%, calc(var(--lightness) * 1%)); }
A razão pela qual queremos armazenar o valor de luminosidade como um número real é para que possamos manipulá-lo com calc
antes de convertê-lo em porcentagem. Por exemplo, se eu quiser escurecer uma cor em 20%
, posso multiplicar sua luminosidade por 0.8
. Podemos tornar isso um pouco mais fácil de ler separando o cálculo de leveza em uma propriedade personalizada com escopo local:
:root { --lightness: 50; } body { --lightness: calc(var(--lightness * 0.8)); background: hsl(25, 80%, calc(var(--lightness) * 1%)); }
Poderíamos até abstrair mais cálculos e criar algo como funções de modificação de cores em CSS usando propriedades personalizadas. Este exemplo provavelmente é muito complexo para a maioria dos casos práticos de temas, mas demonstra todo o poder das propriedades personalizadas dinâmicas.
Simplifique o Tema
Uma das vantagens de usar propriedades personalizadas é a capacidade de simplificar a criação de temas. O aplicativo não precisa estar ciente de como as propriedades personalizadas são usadas. Em vez disso, usamos JavaScript ou código do lado do servidor para definir o valor das propriedades personalizadas. Como esses valores são usados é determinado pelas folhas de estilo.
Isso significa mais uma vez que somos capazes de separar a lógica do design. Se você tiver uma equipe de design técnico, os autores podem atualizar as folhas de estilo e decidir como aplicar as propriedades personalizadas sem alterar uma única linha de JavaScript ou código de back-end.
As propriedades personalizadas também permitem mover parte da complexidade do tema para o CSS e essa complexidade pode ter um impacto negativo na capacidade de manutenção do seu CSS, portanto, lembre-se de mantê-lo simples sempre que possível.
Usando propriedades personalizadas hoje
Mesmo que esteja dando suporte ao IE10 e 11, você pode começar a usar as propriedades personalizadas hoje. A maioria dos exemplos neste artigo tem a ver com como escrevemos e estruturamos CSS. Os benefícios são significativos em termos de manutenibilidade, no entanto, a maioria dos exemplos apenas reduz o que poderia ser feito com código mais complexo.
Eu uso uma ferramenta chamada postcss-css-variables para converter a maioria dos recursos de propriedades personalizadas em uma representação estática do mesmo código. Outras ferramentas semelhantes ignoram propriedades personalizadas dentro de consultas de mídia ou seletores complexos, tratando propriedades personalizadas como variáveis de pré-processador.
O que essas ferramentas não podem fazer é emular os recursos de tempo de execução das propriedades personalizadas. Isso significa que não há recursos dinâmicos, como temas ou alteração de propriedades com JavaScript. Isso pode ser bom em muitas situações. Dependendo da situação, a personalização da interface do usuário pode ser considerada um aprimoramento progressivo e o tema padrão pode ser perfeitamente aceitável para navegadores mais antigos.
Carregando a folha de estilo correta
Há muitas maneiras de usar o postCSS. Eu uso um processo gulp
para compilar folhas de estilo separadas para navegadores mais novos e mais antigos. Uma versão simplificada da minha tarefa gulp
se parece com isso:
import gulp from "gulp"; import sass from "gulp-sass"; import postcss from "gulp-postcss"; import rename from "gulp-rename"; import cssvariables from "postcss-css-variables"; import autoprefixer from "autoprefixer"; import cssnano from "cssnano"; gulp.task("css-no-vars", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssvariables(), cssnano()])) .pipe(rename({ extname: ".no-vars.css" })) .pipe(gulp.dest("./dist/css")) ); gulp.task("css", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssnano()])) .pipe(rename({ extname: ".css" })) .pipe(gulp.dest("./dist/css")) );
Isso resulta em dois arquivos CSS: um normal com propriedades personalizadas ( styles.css
) e outro para navegadores mais antigos ( styles.no-vars.css
). Eu quero que IE10 e 11 sejam servidos styles.no-vars.css
e outros navegadores para obter o arquivo CSS regular.
Normalmente, eu recomendaria o uso de consultas de recursos, mas o IE11 não oferece suporte a consultas de recursos e usamos propriedades personalizadas tão extensivamente que servir uma folha de estilo diferente faz sentido neste caso.
Servir de forma inteligente uma folha de estilo diferente e evitar um flash de conteúdo sem estilo não é uma tarefa simples. Se você não precisar dos recursos dinâmicos das propriedades personalizadas, considere servir todos os estilos do navegador.no styles.no-vars.css
e usar as propriedades personalizadas simplesmente como uma ferramenta de desenvolvimento.
Se você quiser aproveitar ao máximo todos os recursos dinâmicos das propriedades personalizadas, sugiro usar uma técnica CSS crítica. Seguindo essas técnicas, a folha de estilo principal é carregada de forma assíncrona enquanto o CSS crítico é renderizado inline. O cabeçalho da sua página pode ser algo assim:
<head> <style> /* inlined critical CSS */ </style> <script> loadCSS('non-critical.css'); </script> </head>
Podemos estender isso para carregar styles.css
ou styles.no-vars.css
dependendo se o navegador suporta propriedades personalizadas. Podemos detectar suporte como este:
if ( window.CSS && CSS.supports('color', 'var(--test)') ) { loadCSS('styles.css'); } else { loadCSS('styles.no-vars.css'); }
Conclusão
Se você está lutando para organizar CSS de forma eficiente, tem dificuldade com componentes responsivos, deseja implementar temas do lado do cliente ou apenas deseja começar com o pé direito com propriedades personalizadas, este guia deve informar tudo o que você precisa saber.
Tudo se resume a entender a diferença entre variáveis dinâmicas e estáticas em CSS, bem como algumas regras simples:
- Separe a lógica do design;
- Se uma propriedade CSS for alterada, considere usar uma propriedade personalizada;
- Altere o valor das propriedades customizadas, não qual propriedade customizada é usada;
- As variáveis globais são geralmente estáticas.
If you follow these conventions, you will find that working with custom properties is a whole lot easier than you think. This might even change how you approach CSS in general.
Leitura adicional
- “It's Time To Start Using Custom Properties,” Serg Hospodarets
A general introduction to the syntax and the features of custom properties. - “Pragmatic, Practical, And Progressive Theming With Custom Properties,” Harry Roberts
More useful information on theming. - Custom Properties Collection, Mike Riethmuller on CodePen
A number of different examples you can experiment with.