Como tailwindcss define variáveis no CSS

Eai tudo bem? Depois de alguns meses sem postar algo aqui, finalmente chegou a hora de esclarecer algo sobre o tailwindcss.

Abri um tópico no frontendbr/forum#2248 sobre o assunto e até consegui uma breve explicação do criador do tailwindcss.

Mas ficou no ar a tarefa de testar o que Adam disse para ver como realmente funciona e é isso que vou fazer abaixo.

Como o tailwindcss define custom properties

Ao abrir o código final do tailwindcss você verá as custom properties definidas no seletor * asterisco, que tem uma especifidade baixa, mas maior que :root e tem efeito sobre todos os elementos de uma página.

*,:after,:before { /* <-- por que não usam :root aqui? */
    --tw-translate-x: 0;
    --tw-translate-y: 0;
    --tw-rotate: 0;
    --tw-skew-x: 0;
    --tw-skew-y: 0;
    --tw-scale-x: 1;
    --tw-scale-y: 1;
    --tw-pan-x: ;
    --tw-pan-y: ;
    --tw-pinch-zoom: ;
    --tw-scroll-snap-strictness: proximity;
    --tw-ordinal: ;
    --tw-slashed-zero: ;
    --tw-numeric-figure: ;
    --tw-numeric-spacing: ;
    --tw-numeric-fraction: ;
    --tw-ring-inset: ;
    --tw-ring-offset-width: 0px;
    --tw-ring-offset-color: #fff;
    --tw-ring-color: #3b82f680;
    --tw-ring-offset-shadow: 0 0 #0000;
    --tw-ring-shadow: 0 0 #0000;
    --tw-shadow: 0 0 #0000;
    --tw-shadow-colored: 0 0 #0000;
    --tw-blur: ;
    --tw-brightness: ;
    --tw-contrast: ;
    --tw-grayscale: ;
    --tw-hue-rotate: ;
    --tw-invert: ;
    --tw-saturate: ;
    --tw-sepia: ;
    --tw-drop-shadow: ;
    --tw-backdrop-blur: ;
    --tw-backdrop-brightness: ;
    --tw-backdrop-contrast: ;
    --tw-backdrop-grayscale: ;
    --tw-backdrop-hue-rotate: ;
    --tw-backdrop-invert: ;
    --tw-backdrop-opacity: ;
    --tw-backdrop-saturate: ;
    --tw-backdrop-sepia:
}
      

O que me chamou a atenção foi o seletor usado *,:after,:before porque não usar :root?

A natureza das propriedades definidas

Notei que todas as propriedades como --tw-scale-x, --tw-rotate, --tw-translate-x são usadas em propriedades de CSS que tem multiplos valores ou funções aplicáveis em uma só propriedade.

Por exemplo, a propriedade transform é usada com 1 ou mais funções como valor:

.transform {
  transform: scale(1.5) rotate(45deg) translateX(10px);
}


Veja abaixo, como a classe .rotate-45 é definida no tailwindcss:

.rotate-45
  --tw-rotate: 45deg;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y))
            rotate(var(--tw-rotate))
            skewX(var(--tw-skew-x))
            skewY(var(--tw-skew-y))
            scaleX(var(--tw-scale-x))
            scaleY(var(--tw-scale-y));
}

Separei o valor do transform linha a linha para ficar mais fácil de ver.

Pois essa funcionalidade do CSS de aceitar multiplas funções como valor de uma só propriedade é a razão pela qual o tailwindcss decidiu usar o seletor *,*:before,*:after para como o author explicou no tweet para garantir que a custom property seja redefinida em cada elemento e que não sejam herdadas.

Reproduzindo o problema que ele resolve

Como a classe .rotate-45 altera o valor da custom property --rotate e no CSS as variáveis são herdadas pelos elementos filhos, o exemplo abaixo pode ser um cenário válido para reproduzir o problema que o tailwindcss resolveu.

:root { /*<-- note que usei :root e não * */
  --rotate: 0;
}

.rotate-45 {
  --rotate: 45deg;

  transform: rotate(var(--rotate));
}       

.rotate {
  transform: rotate(var(--rotate));
}       
          
<div class="rotate-45">
  ........45deg
  
  <div class="rotate">
    ........90deg
    
    
  <div class="rotate">
    ........135deg
  </div>
  </div>
  
</div>
      

Demonstração abaixo:

........45deg
........90deg
........135deg

Qual é o problema?

Faz sentido que cada elemento rode 45 graus, porém como cada classe do tailwindcss irá aplicar todas as variáveis de transform, os valores das custom properties precisam ser redefinidas.

Caso contrário, o elemento pai tem um rotate de 45 graus um dentro do outro indo para 90 graus e 135 graus.

Porém, se o elemento filho tiver apenas um scale de 1.5 sem rotação, por causa que a classe rotate-45 muda o valor da custom property que será herdada pelos filhos, o elemento filho também terá uma rotação de 45 graus "indesejada" junto com o scale desejado.

Solucionando o problema

Usar o seletor asterisco para redefinir o valor da custom property em cada elemento filho.

Usando o mesmo HTML anterior, porém com este CSS:

* { /*<-- note que usei * e não :root*/
      --rotate: 0;
    }
    
    .rotate-45 {
      --rotate: 45deg;
      transform: rotate(var(--rotate));
    }

    .rotate {
      transform: rotate(var(--rotate));
    }

        

Temos a demonstração abaixo:

........45deg
........90deg
........135deg

Conclusão

TailwindCSS tem um desafio que é "como criar classes atômicas de CSS para afetar a propriedade transform" e a solução altural faz uso extensivo de variáveis/custom properties.

De tal forma que alguns tipos de custom propriedades precisam ser definidas no seletor asterisco, para de certa forma burlar a herança de variáveis que é uma funcionalidade do CSS.

Quem quiser discutir sobre o assunto, por favor deixe um comentário lá no fórum frontendbr/forum#2248 onde a discussão começou.