Tableless

Busca Menu

Semântica de variáveis e propriedades personalizadas em CSS

Seja o primeiro a comentar por

Pré-processadores

Sou defensor do uso de pré-processadores de CSS e tenho alguns bons argumentos para convencer você. Um dos benefícios dos pré-processadores é a possibilidade de uso de variáveis para armazenar medidas, cores e outros atributos do layout.

Além de manter seu projeto facilmente configurável e permitir o uso de álgebra, a simples tarefa de dar nome aos atributos força um raciocínio mais apurado. Variações pequenas de medidas e cores, que são resquícios das ferramentas utilizadas para design, serão mais facilmente identificadas e limadas.

There are only two hard things in Computer Science: cache invalidation and naming things.
— Phil Karlton

A semântica, de maneira geral, descreve o significado dos tokens da linguagem. Em nosso caso, a semântica descreve quais as transformações que nosso código irá sofrer durante o processamento.

Diferentes pré-processadores possuem significativas diferenças no comportamento de variáveis. Um aspecto importante é entender qual o comportamento das variáveis em meio a estas transformações a fim de evitar resultados inesperados.

Introdução

Seletores aninhados são a principal influência no comportamento de variáveis em pré-processadores. Variáveis definidas em um nível são visíveis para todos os níveis aninhados mais internamente.

.list {
    $var-red: crimson;
    .list-item {
        color: $var-red;
    }
}

Quando definimos uma variável fora de qualquer seletor, esta será visível em qualquer ponto do código. Chamamos estas variáveis de globais.

$global-red: crimson;
.list-item {
    color: $global-red;
}

Sass

Vejamos a semântica das variáveis no Sass utilizando um exemplo. Após definido o valor de uma variável em um seletor, uma nova atribuição em um aninhamento interno terá efeito também nos níveis externos. Este comportamento pode ser um pouco estranho, veja em nosso exemplo como o seletor .just-to-confuse-you modifica o valor de $orange afetando inclusive o valor da variável no seletor .is-orange. O caso da variável global $red também é curioso, qualquer ponto do código poderá modificar seu valor afetando em todos os seus usos subsequentes indiferente do nível de aninhamento do seletor.

Caso tenha um bom domínio de JavaScript, uma analogia pode ajudar no seu entendimento. Pense no aninhamento dos seletores como escopos criados por funções imediatamente invocadas. A primeira atribuição de valor a uma variável também a declara utilizando var. As demais atribuições de valor em escopos mais internos não fazem mais uso do var. Veja, closures que podem alterar o valor das variáveis dos escopos em que são criadas afetando todos os demais escopos. Variáveis que tem seu primeiro valor atribuído fora de qualquer seletor serão globais, nunca farão uso de var.

Este comportamento é um tanto estranho e algumas vezes criticado. Um seletor aninhado pode acidentalmente atribuir valor a uma variável global e impactar no restante do projeto. Pensando nisto, as variáveis globais não poderão mais ser alteradas dentro de seletores nas próximas versões do pré-processador. A versão 3.3.0 já inclui a flag !global. Nesta versão, já é acusado um warning se a flag não for usada ao alterar o valor de uma global dentro de um seletor.

Vale lembrar que existe também flag !default para os casos em que a intenção seja atribuir valor a uma variável apenas se está ainda não tiver recebido algum.

LESS e Stylus

A semântica de variáveis no LESS e Stylus é um tanto mais previsível. Retomando a nossa analogia com o universo JavaScript, pense como se cada atribuição de valor fosse precedida de uma declaração de variável utilizando var. O valor do escopo mais externo será ofuscado mas não alterado.

Voltando aos pré-processadores, um seletor não terá a capacidade de alterar o valor de uma variável global ou definida em um seletor de nível mais externo. Este apenas definirá uma nova variável visível no nível atual e nos níveis mais internos. Mais simples e seguro, porém um tanto menos poderoso que no Sass. Confira o exemplo em LESS (na dúvida, aqui está em Stylus).

O LESS não dispõe de nenhuma flag como !default e !global, uma pena.

Variáveis e os mixins

Os mixins também possuem um papel importante na manipulação de variáveis, tanto no LESS quanto no Sass, o escopo levado em consideração é aquele em que o mixin é incluído. Isto é útil para sistemas semânticos de grids em porcentagem, por exemplo. Confira o código do Bourbon Neat e veja como os mixins utilizam variáveis para compartilhar valores entre si.

Curiosamente, os mixins do Stylus têm acesso as variáveis do seletor em que são incluídos, porém não possuem capacidade de alterar seus valores. No Stylus, os mixins constituem um escopo que impossibilita alterar valores de variáveis de escopos externos.

A semântica de comportamento das variáveis segue a já ilustrada para cada um dos pré-processadores. Portanto, os mixins de Sass são um tanto mais poderosos e destrutivos por poderem manipular variáveis globais mesmo quando incluídos em um seletor.

Variáveis nativas de CSS

Os pré-processadores de CSS mais populares já tem pouco mais de meia década de existência. Neste tempo, seus benefícios puderam ser comprovados e naturalmente rascunhos de especificações surgiram para incorporar algumas das suas funcionalidades ao CSS.

O primeiro rascunho de especificação de variáveis nativas no CSS data de 2012. Esta especificação é bastante comparada com as variáveis já presentes nos pré-processadores, inclusive por fontes renomadas. Na minha opinião, a comparação é equivocada, as variáveis nativas possuem uma semântica e uso completamente diferente das que temos nos pré-processadores.

O último rascunho da especificação é intitulado CSS Custom Properties e isto já nos dá uma boa pista da diferença de comportamento. As variáveis nativas são propriedades com característica de herança igual a color e font: seus valores são herdados por padrão pelos filhos do elemento em que são definidas. Parece simples, mas esta é a grande diferença, o comportamento variáveis nativas é apoiado nos elementos do documento assim como o restante da folha de estilo.

Propriedades Personalizadas

A partir de agora, passaremos a chamar nossas variáveis nativas de propriedades personalizadas a fim de seguir a especificação e expressar melhor seu significado. A propriedade personalizada é aquela cuja definição é precedida de --. Seus valores podem ser acessados através da função var(). Vejamos um primeiro exemplo.

.list {
    --red: crimson;
    .list-item {
        color: var(--red);
    }
}

Como você pode imaginar, o conceito de variável global não mais se aplica às propriedades personalizadas. Uma certa confusão pode ser causada ao analisar trechos de código como este abaixo que inclusive fazem parte da especificação.

:root {
--red: crimson;
}

Não há nenhuma relação com variáveis globais. O :root trata-se de um seletor da especificação level 3 que referencia o mais externos dos elementos do documento. Na prática, este seletor pode ser substituído pelo seletor html. Uma das razões da criação do :root é a de que folhas de estilo podem ser utilizadas em documentos no formato SVG e XML, o que não vem muito ao caso.

Componentes

O ponto forte das propriedades personalizadas está enraizado justamente na herança. Um dos assuntos mais mencionados nos últimos tempos no desenvolvimento front-end são os componentes. São nos componentes que as propriedades personalizadas ganham destaque.

A melhor maneira de provar as vantagens de uma tecnologia é resolvendo um problema real. Tenho em memória um problema que enfrentei em uma plataforma de ensino. Nesta plataforma de ensino, haviam cerca de dez disciplinas, cada uma identificada com uma cor. Os componentes da plataforma ganhavam a caracterização de uma das disciplinas e as cores da disciplina eram utilizadas em diferentes propriedades como background, color, box-shadow e border-color em elementos distintos do componente. Não existe uma maneira sensata de solucionar um problema como este, a solução é duplicar bastante código. As propriedades personalizadas nos permitem resolver de uma maneira incrível, vejam o exemplo:

/* Component lesson */
.lesson {
    color: grey;
    border: 1px solid var(--discipline-color);
}
.lesson-title {
    color: var(--discipline-color);
}
.lesson-grade {
    background: var(--discipline-color);
}

/* Disciplines */
.discipline-mathematics {
    --discipline-color: blue;
}

.discipline-portuguese {
    --discipline-color: red;
}

Este exemplo pode ser aplicado muito facilmente ao HTML dos componentes, basta adicionarmos uma das classes de disciplina a cada um deles: <div class="lesson discipline-mathematics">. Veja o exemplo em funcionamento (apenas Firefox Nightly).

Palavras finais

As propriedades personalizadas não substituem as variáveis dos pré-processadores. As variáveis dos pré-processadores continuarão cumprindo sua função de abstrair aspectos de layout descomplicando sua organização de código. As propriedades personalizadas deverão, por sua vez, facilitar a customização de componentes. O ideal é aprendermos a utilizar estes dois recursos em conjunto.

A especificação de CSS Custom Properties ainda está em rascunho e só está implementada por enquanto no Firefox Nightly. Apesar de ainda precisarmos esperar um pouco para utilizar em produção, é importante já raciocinarmos quais os problemas que as propriedades solucionam. Lembre-se que as variáveis de pré-processadores já estão por ai há um bom tempo e não tem porque deixar de usá-las.

Espero que este texto o tenha ajudado a entender a diferente semântica das variáveis em cada um dos pré-processadores e diferenciado o suficiente das propriedades personalizadas.

Publicado no dia