Os limites do Tailwind CSS

Camilo Micheletto - Sep 25 '23 - - Dev Community

Taiwind como tudo nessa vida tem seus prós e contras. É importante que um ecossistema tenha um bom equilíbrio de críticas e elogios pra se manter num processo de melhoria contínua e também munir seus usuários com opiniões diversas que potencializem as suas tomadas de decisão sobre usar ou não.

Nesse artigo eu vou tratar o Tailwind como uma ferramenta, não como uma divindade, um problema grave ou solução definitiva. O que espero é que esse artigo ajude a tomada de decisão de aplicar Tailwind num projeto seja do ponto de vista de arquitetura de aplicação e levando em consideração seus prós e contras, e não por sentimentos puramente individuais e pessoais como "DX" ou "facilidade".

Esse artigo expressa somente a minha opinião sob meu critério técnico pessoal. Como eu me sinto sobre Tailwind ou sua comunidade foi algo secundário pra mim ao escrever esse artigo, espero o mesmo das pessoas que irão ler esse artigo.


O que é Tailwind CSS

Página inicial do site Tailwind. O slogan "Rapidly build modern websites without ever leaving your HTML" em caixa alta.

Tailwind é um framework de CSS utilitário. Um framework porque ele por si só não é uma utilidade, mas um pequeno ecossistema intricado ao redor de um design system. Utilitário pois ele fornece uma série de classes com responsabilidade única que trazem estilos com uma boa direção de arte, minimizando as decisões de design que uma pessoa desenvolvedora pode acabar tendo que tomar e que podem dificultar a chegada na etapa de desenvolvimento.


Pontos positivos

Mais do que um framework de CSS o Tailwind é um plugin de PostCSS. Isso porque seus maiores benefícios é a geração de classes JIT (Just-in-time) e a facilidade de purging, prefixing e outras ferramentas de otimização e compatibilidade que vem direto da caixa.

 

JIT
Just-in-time nasceu na v2.1 do Tailwind e é a capacidade de gerar estilos sob demanda. Ao passar os pontos de entrada de estilos no seu arquivo de configuração, Tailwind gera apenas os estilos que foram declarados nos seus arquivos de template (HTML, JSX, etc), fazendo com que classes não utilizadas não existam em produção.

Purging
Purge é a remoção de CSS não utilizado. O algoritmo de purge tem que levar em consideração uma série de fatores, o que pode dificultar o processo de remoção pra códigos de CSS mais denso ou com CSS condicional/ injetado via script. O Tailwind limita uma série de particularidades do CSS e fecha o usuário no seu ecossistema de tokens e classe, dando total previsibilidade pro PurgeCSS realizar uma remoção segura.

Otimização e compatibilidade
Tailwind usa minificação e compressão pra entregar bundles de CSS otimizados. Estratégias que inclusive podem e devem ser aplicadas à qualquer abordagem de CSS que não as tenha por padrão. Tailwind utiliza Gzip e Brotli pra compressão e CSSnano pra minificação.

DX
Developer experience é a qualidade de minimização de atritos no processo de desenvolvimento, tornando a experiência de programar fluída e quase "sem pensar". Por seu ecossistema de plugins, presets e extensões, é possível programar interfaces de forma rápida com código que se autocompleta com as classes disponíveis, quase que como tipado, com uma documentação bem visual e abundante, e muitas soluções e abstrações criadas pela comunidade.


Quando aplicar o Tailwind CSS no meu projeto?

Tailwind é extremamente útil pra projetos que possuem nenhum design system ou um que seja pouquíssimo opinionado. É perfeito pra projetos com necessidade de CSS de baixa complexidade e baixíssima manutenção, ideal pra times de front-end com pouca expertise em CSS ou com baixa capacidade de alocação de custos pra arquitetura de CSS.

 

Se você ficou na dúvida sobre os termos design system ou design tokens, escrevi um artigo bacana sobre isso.

 

Quando falo sobre a aplicabilidade, não estou dizendo que é impossível aplicar em uma aplicação de larga escala ou com design systems, mas ao fazê-lo, o time irá lidar muito mais com os pontos que o Tailwind não cobre, fazendo com que abstrações, patches e complexidade extra sejam criadas.

🧐 Se você tem bons exemplos de uma aplicação nesses parâmetros que deu certo com pouquíssima complexidade, comenta sobre, vou adorar conhecer!


Pontos negativos

Me incomoda muito quem critica qualquer coisa sem usar ou com base em critérios levianos. Pra escrever esse artigo eu testei Tailwind de duas formas - criei uma aplicação pra testar algoritmos complexos de UI e li o código fonte de dois frameworks populares que utilizam o Tailwind, o shadcnUI e o DaisyUI.

A escolha se deu pois um usa diretamente das classes do Tailwind, o outro as abstraí em classes únicas usando a diretiva @apply, se aproveitando apenas dos design tokens do mesmo.

Uma pessoa fumando cigarro, sendo comparada à fumar vape, paralelamente, código CSS inline sendo comparado à classes tailwind
Eu tava brincando, tá gente?


As críticas já existentes

Adam Wathan tratou sobre uma das críticas recorrentes ao Tailwind que é a separação de interesses (separation of concerns) que é manter uma separação em código das responsabilidades de cada parte da aplicação. Se essa trás benefícios como desacoplamento, manutenabilidade e modularização, ela ainda é uma opinião de alguns escritores dentro de contextos específicos e que deve ser levada em consideração, não uma lei ou axioma.

Há pouca evidência de benefícios mensuráveis da separação do HTML e do CSS pra além do arquivo. E ainda no campo do arquivo, é uma boa prática de performance que o CSS crítico seja colocado em uma tag style de forma inline.

O HTML ilegível é ilegível pra quem não gosta ou não trabalha com Tailwind. Em times que trabalham com esse framework, isso é uma das premissas basilares. Se você reparte o template em páginas e componentes, você precisa revisitar menos os templates e ler espaguetes menores. Eventualmente você acostuma ou instala um linter.

O problema raiz no meu ver antecede o template feio. O problema é a falha estrutural na reutilização de estilos.


Reuso de estilos

Esse trecho leva em consideração majoritariamente o capítulo Reusing Styles da documentação oficial

Tailwind é incapaz lidar com repetição de código por conta própria. Suas formas de lidar com isso são extremamente acopladas à IDE ou à bibliotecas e frameworks de templating ou componentes, como React, Vue ou vintage Nunchucks.

 

Editando via cursor múltiplo na IDE

Uma solução viável, mas que assume o uso de uma IDE e de uma feature específica que tem baixíssima acessibilidade e é pouco viável de se utilizar através de documentos (através do localizar e substituir global) pois este pode selecionar classes além do escopo pretendido. Isso, porém, pode ser solucionado localizando esses escopos com componentes.

Extraindo e componentizando

Componentes podem ser facilmente reutilizados, mas ainda são uma forma de acoplamento que assume pelo menos uma forma rudimentar de templating e pós processamento. Claro que é possível usar Tailwind em projetos vanilla, mas não sem perder os benefícios de JIT, compressão e as diretivas cujo suporte vem totalmente do PostCSS. Sem o PostCSS e o processamento, as maiores qualidades do Tailwind não sobreviveriam. Mesmo num contexto em que componentes são utilizados, o CSS fornece diversas ferramentas de reutilização, como a própria classe não-utilitária e seletores. Tailwind só é capaz de se apropriar através da diretiva @apply.

@apply

Essa diretiva existe pois é a forma mais fácil de cunhar classes que ainda usem do design system do Tailwind. É possível, por exemplo, usar dos design tokens do Tailwind, pois esse utiliza composição de variáveis CSS, mas não as expõe como API, não as documenta, não faz JIT delas standalone, nem possuí autocomplete, fazendo com que o uso das mesmas possa prejudicar a integridade do design system do Tailwind. Sem falar que o próprio Adam percebe o excesso de complexidade e a proeminência de bugs:

 

Novamente, você pode estar ok a respeito disso, principalmente se trabalha com um ecossistema que se encaixa com todos esses requisitos. Entender essa limitação é entender também quando aproveitar melhor quando elas se fazem necessárias.


CSS amordaçado

Muito da DX do Tailwind se deve à abstração de conceitos base do CSS como seletores compostos, unidades CSS, funções de CSS, herança e especificidade. Esses conceitos são sim complexos, mas também possibilitam a escrita de um CSS enxuto, robusto retrocompatível, mas com possibilidade de melhoria progressiva.

Criei 3 casos com diferentes complexidades pra demonstrar.

Layout Holy Grail na abordagem do Josh Comeau

Inspirado no artigo Full-Bleed Layout Using CSS Grid - An elegant solution to a tricky modern layout de Josh Comeau

Layout responsivo com header e footer e o conteúdo em uma coluna central

Esse layout aplicado de forma responsiva e sem media queries é possível com apenas 5 linhas de CSS, 8 se você não utilizar o nesting nativo:

.grail {
  display: grid;
  grid-template-columns: 1fr min(var(--width, 800px), 100%) 1fr;
  & > * { grid-column: 2; }
}
Enter fullscreen mode Exit fullscreen mode

 

Claro que há outras formas de executar esse layout, mas elas implicam em mais CSS - media queries pra controlar o tamanho das laterais de acordo com a largura do dispositivo, ou mais HTML, criando containeres e wrappers usando a mesma lógica do Bootstrap Grid.

Ou seja, pra produzir o mesmo layout você precisa introduzir complexidade que não é necessária, tendo como único benefício "experiência de desenvolvimento".

Você escreve "menos CSS" e não precisa saber algoritmos complicados de grid, mas você tem mais template pra processar, testar e debugar. Se classes utilitárias são simples de debugar por conter apenas uma propridade, páginas ou templates facilmente se tornam um wrapper hell quando o template assume a responsabilidade que os estilos deveriam suprir.

Arbitrary Values
Na versão mais recente, é possível criar uma classe com esses parâmetros sob demanda usando a API de arbitrary values.

<section className="grid w-full grid-cols-[1fr,min(var(--width),100%),1fr] place-items-center gap-y-4 [&>*]:col-start-2">
   <!-- ... -->
</section>
Enter fullscreen mode Exit fullscreen mode

Mas as limitações são claras:

  1. Só é possível se usar uma sintaxe bem específica. A sintaxe não é simples e não dá suporte pra espaçamentos, logo é muito fácil introduzir bugs nessa sintaxe e muito difícil identificá-los.

  2. É possível usar seletores [&>*], mas eles só recebem um parâmetro por vez e o código gerado é praticamente ilegível.

/* Output da classe [&>*] */
.\[\&\>\*\]\:col-start-2>* {
  grid-column-start: 2;
}
Enter fullscreen mode Exit fullscreen mode

 

"Ah, mas o output não importa"
Você não dá valor ao output, mas o navegador não se importa com qual ferramenta você tá utilizando. Se houver algum erro ou inconsistência, você vai enfrentar um devTools com dezenas de classes com seus contextos encapsulados e outras com declarações ilegíveis.


Fica, vai ter segunda parte

Há muita coisa a ser dita e exemplos a serem explorados, então farei uma segunda parte analisando mais uma aplicação de CSS avançado, se o Tailwind realmente produz menos CSS, a questão da herança e especificidade e a interação do Tailwind com outros frameworks e código estrangeiro.

Adoraria ler a opinião de vocês até aqui, os desafio a deixarem de lado questões pessoais e favoritismos, pois eu particularmente aprendi muito tecnicamente ao analisar essa ferramenta de forma fria, e gostaria que o mesmo acontecesse com vocês. Não sejam que nem o Adam:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .