FS
Simplificando a estilização TailwindCSS com Tailwind Merge e Variants
A tríade de estilização TailwindCSS
TailwindCSS possui uma grande comunidade e vem sendo adotado cada vez mais em aplicações web Frontend. Alguns grandes frameworks como NextJS já possuem o Tailwind configurado por padrão nas suas aplicações, sem necessidade de configurações adicionais para fazer com que ele funcione. Mas o objetivo aqui hoje não é falar do Tailwind em si, ou como utilizá-lo e configurá-lo, mas sim de duas ferramentas auxiliares excepcionais que vão simplificar muito o uso desse framework CSS.
Alguns problemas na estilização de componentes
Se pensarmos sobre componentes React nos deparamos com alguns problemas, que serão citados antes de explicarmos o objetivo dessas duas bibliotecas.
- Os componentes React, como sabemos, podem ser uma página inteira ou um simples botão. Muitas vezes utilizamos o mesmo componente com pequenas alterações de estilo, assim acabamos usando props para estilizá-los ou, em grandes alterações, até criamos um novo componente, pois passar todas as alterações que queremos via props seria insustentável.
- Outro problema é o uso de propriedades repetidas, deixando o código rendundante e muito verboso. Algumas vezes precisamos estilizar um <button> e uma tag <a> com o mesmo estilo, assim repetimos a mesma estilização.
Tailwind Merge
O Tailwind Merge vem resolver este nosso primeiro problema. Como o próprio nome diz, ele mescla a estilização dos componentes, principalmente quando queremos mudar uma variável que já foi definida, para que não haja conflito de estilos. Exemplo:
export const Botao = ({ className }: { className?: string }) => {
return (
<button className={`bg-pink-400 p-4 border border-black text-white ${className}`}>
Clique aqui
</button>
)
}Este é um simples componente de botão onde temos uma cor de fundo rosa, uma borda preta e um texto branco. Ele recebe uma prop chamada className onde pode receber mais estilizações via props em outros componentes. Mas vamos tentar passar uma nova cor de fundo para esse botão via prop:
export const Container = () => {
return (
<div>
<Botao className="bg-blue-500" />
</div>
)
}Infelizmente a cor do botão pode continuar como pink, que é a estilização que foi passada primeiro, nem sempre a aplicação entende que a propriedade blue deve ser tida como prioridade dessa forma, podendo ou não alterar o valor inicial. Utilizando a prop className neste caso, ao olhar no console do navegador vemos que basicamente estamos fazendo isso:
<button class="bg-pink-400 p-4 border border-black text-white bg-blue-500">Clique aqui</button>
Perceba que a estilização bg está sendo repetida duas vezes e, por mais que a estilização blue esteja sendo citada por último, a aplicação possui dúvidas de qual estilo deve ser renderizado. Algumas vezes isso funciona, mas confesso que não sei dizer bem se neste caso ele considera a estilização por ordem de cascata igual ao CSS, por ordem alfabéfica, enfim, mas este problema pode nos irritar bastante.
Utilizando o Tailwind Merge este problema é resolvido, pois assim podemos estilizar nossos componentes sem sobreposição de estilos. Ele basicamente lida com prioridades de estilos, removendo estilizações que podem entrar em conflito, assim sendo possível estilizar um mesmo componente em diferentes pontos do código com pequenas alterações.
Primeiramente vamos intalar o Tailwind-merge com npm i tailwind-merge. Importe twMerge e coloque-o dentro do className do componente que você quer merclar.
import { twMerge } from "tailwind-merge"
export const Botao = ({ className }: { className?: string }) => {
return (
<button className={twMerge(`bg-pink-400 p-4 border border-black text-white`, className)}>
Clique aqui
</button>
)
}Aqui basicamente estamos informando ao nosso componente que queremos merclar as estilizações padrão do tailwind definidas como `bg-pink-400 p-4 border border-black text-white` com a nossa prop className. Agora quando passamos um valor para nossa prop o Tailwind Merge sabe que ele deve mesclar essa nova propriedade com a estilização já presente no componente e que se houver conflito com uma estilização já existente, a definida pela prop prevalecerá.
Agora o nosso botão ficará azul, sem risco de problemas, como denifinido pela nossa prop e o resultado no console será este:
<button class="p-4 border border-black text-white bg-blue-500">Clique aqui</button>
Como podemos ver o bg-pink-400 desapareceu, pois foi mesclado ao bg-blue-500 que foi definido por último, evitando a duplicação e conflito de estilos. Caso não estivessemos utilizando o Tailwind Merge o resultado seria o que vimos anteriormente, como propriedades conflitantes e repetidas:
<button class="bg-pink-400 p-4 border border-black text-white bg-blue-500">Clique aqui</button>
Tailwind Merge - Join
O Tailwind Merge também fornece, além do twMerge o twJoin, para situações específicas quando o usuário somente quer unir propriedades, mas sem mesclar. Neste caso, o usuário só quer unir novos estilos ao componente, mas sem substituir nenhum estilo já definido antes. Por exemplo:
import { twMerge, twJoin } from "tailwind-merge"
export const Botao = ({ className, join }: { className?: string, join?: string }) => {
return (
<div className={twJoin("p-10 bg-gradient-to-b from-white to-zinc-400", join)}>
<button className={twMerge(`bg-pink-400 p-4 border border-black text-white`, className)}>
Clique aqui
</button>
</div>
)
}export const App = () => {
return (
<div className="w-full h-screen flex items-center justify-center">
<Botao join="rounded-2xl shadow-xl shadow-blue-900"/>
</div>
)
}Aqui o join está sendo definido somente para a <div /> que seria o container pai do <button />. As propriedades padrão da div é somente um padding e um background com gradiente. No componente App passamos para a div agora a estilização de altura e largura, um display flex e um alinhamento dos elementos filhos no centro. São estilos que não entram em conflito, são diferentes, assim o join já é o suficiente.
O estilo no console sem o join ficaria assim:
<div class="p-10 bg-gradient-to-b from-white to-zinc-400">
Com os novos estilos, eles só seriam adicionados, sem sobreposição dos anteriores, mesmo que entrem em conflito.
<div class="p-10 bg-gradient-to-b from-white to-zinc-400 rounded-2xl shadow-xl shadow-blue-900">
Atenção, que aqui utilizando twJoin ele só une os estilos, mas não os mescla, assim estilos conflitantes retornarão ao problema que tivemos inicialmente de a aplicação não saber qual estilo priorizar. Mas algumas vezes o twJoin pode ser necessário no lugar do twMerge.
Tailwind Variants
Já o Tailwind Variants funciona de uma forma um pouco diferente. Ele já cria estilos pré-definidos como base e variações (variants) para elementos que podem ter alguma diferença. Vamos utilizar novamente o nosso exemplo do botão, mas primeiramente vamos instalar nossa dependência com npm install tailwind-variants.
Agora precisamos primeiramente criar uma variável com um esquema do tv(Tailwind Variants):
import { tv } from "tailwind-variants"
const button = tv({
base: "p-4 border border-black text-black",
variants: {
colors: {
pink: "bg-pink-500",
blue: "bg-blue-500",
},
shadow: {
dark: "shadow shadow-black",
light: "shadow shadow-pink-600"
}
}
})Aqui estamos criando as estilizações padrão dos nossos botões. Queremos dizer que todos os nossos botões terão o estilo base "p-4 border border-black text-black" e que podem receber variações de color ou shadow como definimos nas nossas variants. Assim, nas variações de colors temos pink onde o botão receberá um background na cor rosa ou blue, onde o botão irá receber a estilização de background na cor azul. Inicialmente nosso botão não apresenta nenhum estilo:
export const Botao = () => {
return (
<button className={""}>
Clique aqui
</button>
)
}<button class="">Clique aqui</button>
Para configurarmos isso dentro do nosso componente é relativamente similar ao Tailwind Merge:
export const Botao = () => {
return (
<button className={button()}>
Clique aqui
</button>
)
}<button class="p-4 border border-black text-white">Clique aqui</button>
Dessa forma, sem adicionar nenhuma variação, nosso botão irá receber somente a estilização base. Caso queiramos adicionar variações no botão devemos abrir um objeto dentro da invocação da variável button() e mencionar qual variação queremos, dessa forma:
export const Botao = () => {
return (
<button className={button({colors: "blue", shadow: "light"})}>
Clique aqui
</button>
)
}<button class="p-4 border border-black text-white bg-pink-100 shadow shadow-pink-600">Clique aqui</button>
Aqui definimos que nosso botão também receberá os estilos das variáveis de cor no estilo azul, e sombra no estilo claro. Definimos essas variações no estilo chave e valor (chave: "valor" = colors: "blue").
Útil também caso queiramos definir o mesmo componente com as variantes diferentes em um componente pai:
import { tv } from "tailwind-variants"
const button = tv({
base: "p-4 border border-black text-black",
variants: {
colors: {
pink: "bg-pink-500",
blue: "bg-blue-500",
zinc: "bg-zinc-500"
}
}
})
export const App = () => {
return (
<div className="w-full h-screen flex flex-col gap-5 items-center justify-center">
<button className={button({colors: "pink"})}>Clique aqui</button>
<button className={button({colors: "blue"})}>Ou aqui</button>
<button className={button({colors: "zinc"})}>Talves aqui</button>
</div>
)
}Tailwind Variants - className
O Tailwind Variants também apresenta suporte para utilização de classes adicionais que podem ser definidas mesmo já recebendo a estilização base ou as variáveis do tv. Caso ainda seja necessário fazer uma pequena modificação no elemento, é possível utilizar a variável className para definir novos estilos além dos já definidos:
export const Botao = () => {
return (
<button className={button({className: "bg-green-400 rounded-lg"})}>
Clique aqui
</button>
)
}<button class="p-4 border border-black text-white bg-green-400 rounded-lg">Clique aqui</button>
O Tailwind Variants pode agir de forma similar ao Tailwind Merge, pois ao definir um className o tv irá mesclar as propriedades base com as propriedades definidas no className e sendo este a prioridade. Por exemplo, mesmo definindo a variação de cor ao botão, mas alterando manualmente no className para uma cor diferente, o className irá prevalecer:
import { tv } from "tailwind-variants"
const button = tv({
base: "p-4 border border-black text-black",
variants: {
colors: {
pink: "bg-pink-500",
},
}
})
export const Botao = ({className}: {className?: string}) => {
return (
<button className={button({className: "bg-green-500", colors: "pink"})}>
Clique aqui
</button>
)
}<button class="p-4 border border-black text-white bg-green-500">Clique aqui</button>
Tailwind Merge + Tailwind Variants
Também é possível unir ambos, caso seja necessário pré-definir um elemento no interior do componente com o Tailwind Variants, mas ser mais viável estilizar um elemento diretamente dentro de um componente pai, no caso de estilizarmos o mesmo componente várias vezes com estilos diferentes. Segue o exemplo:
import { twMerge } from "tailwind-merge"
import { tv } from "tailwind-variants"
const button = tv({
base: "p-4 border border-black text-black",
variants: {
colors: {
pink: "bg-pink-500",
},
}
})
export const Botao = ({className}: {className?: string}) => {
return (
<button className={twMerge(button({colors: "pink"}), className)}>
Clique aqui
</button>
)
}Aqui vinculamos o Tailwind Merge adicionando o Tailwind Variants como estilização inicial e mesclando com a prop className que pode ser utilizada para estilizar este trecho em um outro componente pai. É útil para caso precisemos estilizar o mesmo componente de várias formas:
import { Botao } from "./components/botao"
export const App = () => {
return (
<div className="w-full h-screen flex flex-col gap-5 items-center justify-center">
<Botao className="bg-blue-500"/>
<Botao className="bg-red-500"/>
<Botao className="bg-green-500"/>
</div>
)
}Conclusão
- Tailwind Merge possui duas opções:
- twMerge: Responsável por mesclar estilos evitando conflitos
- twJoin: União de estilos, somente, mas conflitos ainda podem ocorrer, caso haja duas estilizações definindo a mesma propriedade com valores diferentes
- Tailwind Variants:
- Útil para criação de estilizações pré-definidas
- Útil para criação de variações de estilos, sendo útil para não precisar repetir código, pois os estilos e suas variações já estão definidas em outro arquivo.
- Juntos:
- Ainda são úteis trabalhando juntos, um não elimina a funcionalidade do outro, podendo ser útil para definir as propriedades de um componente em um componente pai utilizando classes e twMerge do Tailwind Merge e dentro do componnete os estilos serem definidos com o tv do Tailwind Variants. Isso é útil quando não queremos somente estilizar um elemento, mas todo um componente que já possui outras funcionalidades. Dessa forma somente o tv não seria o suficiente sozinho
Espero ter ajudado vocês a compreender um pouco mais sobre o universo do Tailwind e como ainda sabemos tão pouco sobre essa tecnologia. Se tiver interesse nessas duas ferramentas acesse as suas documentações abaixo:
Post criado/atualizado em: 08/10/2024
Autor: Felipe Santiago
Trabalho com desenvolvimento web Frontend desde 2023 com foco em React e Typescript. TailwindCSS foi uma reviravolta em meus projetos, não consigo mais ficar sem ele. Evoluindo para aplicações FullStack, foquei muito para criação de APIs com Node e Fastify/Express e manutenção de banco de dados como PostgreSQL.