Como usar React com Tailwind CSS: Guia Completo
Introdução
Se você trabalha com React há algum tempo, provavelmente já passou por aquele momento em que o arquivo CSS do projeto cresce sem controle, os nomes de classes se tornam inconsistentes entre componentes, e qualquer refatoração visual vira um exercício de arqueologia no código. Tailwind CSS surgiu como uma resposta a esse problema, e com a chegada da versão 4, a integração com React ficou ainda mais poderosa e simples.
Neste guia, vou mostrar como configurar e usar React com Tailwind CSS v4 de forma prática. Não vou ficar na teoria: vamos passar por setup, exemplos reais de código em TypeScript, padrões que funcionam em produção e armadilhas que você deve evitar. Se você já tem pelo menos dois anos de experiência com desenvolvimento web, vai conseguir aplicar tudo isso no seu próximo projeto.
Por que usar React com Tailwind CSS
Produtividade real no dia a dia
A combinação de React com Tailwind CSS resolve um problema fundamental: a colocalização de estilos com lógica de componentes. Em vez de alternar entre arquivos CSS e arquivos de componente, você declara a aparência diretamente no JSX. Isso parece trivial, mas o impacto na velocidade de desenvolvimento é enorme.
Com React, você já pensa em termos de componentes reutilizáveis. Tailwind complementa essa mentalidade porque cada componente carrega consigo toda a informação visual que precisa. Não existe mais aquele cenário em que você deleta um componente e deixa CSS órfão no projeto.
Performance com Tailwind v4
A versão 4 do Tailwind CSS é uma reescrita completa do framework. A engine agora roda em Rust (usando Lightning CSS), o que torna os builds até 10 vezes mais rápidos comparados à v3. Além disso, o arquivo tailwind.config.js foi eliminado em favor de configuração diretamente no CSS. Isso simplifica o setup e reduz a quantidade de arquivos de configuração no projeto.
Casos de uso onde essa combinação brilha
Na prática, React com Tailwind CSS se destaca em cenários específicos: dashboards administrativos com dezenas de componentes de UI, landing pages que precisam de iteração visual rápida, design systems internos de empresas, e aplicações SaaS onde a consistência visual entre telas é crítica. Se você trabalha com Next.js e Server Actions, a combinação fica ainda mais interessante porque o Tailwind se integra perfeitamente com o ecossistema.
Setup passo a passo: React com Tailwind CSS v4
Criando o projeto com Vite e React
Vamos usar Vite como bundler porque é o padrão mais comum para projetos React modernos. Execute os seguintes comandos:
// Terminal: criando o projeto
// npm create vite@latest meu-projeto -- --template react-ts
// cd meu-projeto
// npm installInstalando o Tailwind CSS v4
No Tailwind v4, a instalação ficou mais enxuta. Você precisa apenas do plugin para Vite:
// Terminal: instalando Tailwind CSS v4
// npm install tailwindcss @tailwindcss/viteEm seguida, registre o plugin no arquivo vite.config.ts:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [
react(),
tailwindcss(),
],
});Configurando o CSS principal
No Tailwind v4, toda a configuração acontece no próprio arquivo CSS. Abra o arquivo src/index.css e substitua o conteúdo por:
/* src/index.css */
@import "tailwindcss";
/* Customizações com a nova sintaxe do v4 */
@theme {
--color-brand-primary: #2563eb;
--color-brand-secondary: #7c3aed;
--font-family-display: "Inter", sans-serif;
--breakpoint-3xl: 1920px;
}Perceba: não existe mais tailwind.config.js. A diretiva @theme substitui a seção theme.extend que você usava na v3. Isso é uma das mudanças mais significativas da nova versão.
Exemplo prático 1: componente de Card reutilizável
Vamos construir um componente de Card que você encontraria em qualquer aplicação real, como um dashboard de métricas ou uma listagem de produtos.
import { type ReactNode } from "react";
interface CardProps {
title: string;
description: string;
badge?: string;
icon?: ReactNode;
variant?: "default" | "highlighted" | "danger";
onClick?: () => void;
}
const variantStyles: Record<string, string> = {
default: "bg-white border-gray-200 hover:border-gray-300",
highlighted: "bg-blue-50 border-brand-primary hover:shadow-lg",
danger: "bg-red-50 border-red-300 hover:border-red-400",
};
export function Card({
title,
description,
badge,
icon,
variant = "default",
onClick,
}: CardProps) {
return (
<div
onClick={onClick}
className={`
rounded-2xl border p-6
transition-all duration-200 ease-in-out
cursor-pointer
${variantStyles[variant]}
`}
>
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
{icon && (
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 text-gray-600">
{icon}
</div>
)}
<div>
<h3 className="text-lg font-semibold text-gray-900 font-display">
{title}
</h3>
<p className="mt-1 text-sm text-gray-500 leading-relaxed">
{description}
</p>
</div>
</div>
{badge && (
<span className="inline-flex items-center rounded-full bg-brand-primary/10 px-3 py-1 text-xs font-medium text-brand-primary">
{badge}
</span>
)}
</div>
</div>
);
}Repare em alguns detalhes importantes nesse código. Primeiro: a variável variantStyles mapeia as variantes para classes do Tailwind. Isso mantém o JSX limpo e facilita adicionar novas variantes sem modificar a estrutura do componente. Segundo: a classe font-display funciona porque definimos --font-family-display no bloco @theme do CSS. E terceiro: a cor bg-brand-primary/10 usa a cor customizada com opacidade de 10%, algo que o Tailwind v4 suporta nativamente.
Exemplo prático 2: formulário de busca com estado e responsividade
Agora vamos para algo mais completo, um componente de busca com estado, tratamento de loading e layout responsivo:
import { useState, useCallback, useRef, useEffect } from "react";
interface SearchResult {
id: string;
title: string;
category: string;
}
interface SearchBarProps {
onSearch: (query: string) => Promise<SearchResult[]>;
placeholder?: string;
}
export function SearchBar({
onSearch,
placeholder = "Buscar...",
}: SearchBarProps) {
const [query, setQuery] = useState("");
const [results, setResults] = useState<SearchResult[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const handleSearch = useCallback(
async (value: string) => {
setQuery(value);
if (value.length < 2) {
setResults([]);
setIsOpen(false);
return;
}
setIsLoading(true);
try {
const data = await onSearch(value);
setResults(data);
setIsOpen(data.length > 0);
} catch (error) {
console.error("Erro na busca:", error);
setResults([]);
} finally {
setIsLoading(false);
}
},
[onSearch]
);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
containerRef.current &&
!containerRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
return (
<div ref={containerRef} className="relative w-full max-w-xl mx-auto">
<div className="relative">
<input
type="text"
value={query}
onChange={(e) => handleSearch(e.target.value)}
placeholder={placeholder}
className="
w-full rounded-xl border border-gray-300
bg-white px-4 py-3 pl-12
text-sm text-gray-900 placeholder-gray-400
shadow-sm
transition-all duration-150
focus:border-brand-primary focus:outline-none focus:ring-2 focus:ring-brand-primary/20
md:text-base md:py-3.5
"
/>
<svg
className="absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
{isLoading && (
<div className="absolute right-4 top-1/2 -translate-y-1/2">
<div className="h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-brand-primary" />
</div>
)}
</div>
{isOpen && (
<div
className="
absolute z-50 mt-2 w-full
rounded-xl border border-gray-200
bg-white shadow-xl
overflow-hidden
"
>
<ul className="max-h-80 overflow-y-auto py-2">
{results.map((result) => (
<li
key={result.id}
className="
flex items-center justify-between
px-4 py-3
cursor-pointer
transition-colors duration-100
hover:bg-gray-50
"
>
<span className="text-sm font-medium text-gray-900">
{result.title}
</span>
<span className="text-xs text-gray-400 bg-gray-100 rounded-full px-2 py-0.5">
{result.category}
</span>
</li>
))}
</ul>
</div>
)}
</div>
);
}Esse componente demonstra vários padrões reais: responsividade com prefixos md:, animações com animate-spin e transition, posicionamento absoluto para o dropdown, e estados visuais para focus e hover. Tudo isso sem escrever uma única linha de CSS customizado.
Boas práticas para React com Tailwind CSS
Organize classes longas com consistência
Quando as strings de classe ficam extensas, adote uma ordem fixa. Uma convenção que funciona bem: layout (display, position, flex) primeiro, depois sizing (width, height, padding, margin), depois visual (colors, borders, shadows), e por último interatividade (hover, focus, transitions).
Extraia componentes, não classes
Um erro comum é tentar usar @apply para criar classes utilitárias customizadas. Na maioria dos casos, a abordagem correta com React é extrair um componente. Se você está repetindo o mesmo grupo de classes em vários lugares, crie um componente Button, Badge ou Input. Componentes React já são a abstração certa para reutilização.
Use a diretiva @theme para design tokens
No Tailwind v4, a diretiva @theme é onde você define as cores, fontes e espaçamentos do seu projeto. Trate esse bloco como a fonte de verdade do seu design system. Quando um designer mudar a cor primária da marca, você altera em um único lugar.
Cuidado com classes dinâmicas
Tailwind funciona com detecção estática de classes. Isso significa que construções como bg-${color}-500 não funcionam porque o bundler não consegue identificar a classe em tempo de build. Sempre use o mapa completo de classes, como fizemos no exemplo do componente Card com o objeto variantStyles.
Considere bibliotecas auxiliares
Para projetos maiores, considere usar clsx ou tailwind-merge para combinar classes condicionais de forma mais limpa. O tailwind-merge é especialmente útil porque resolve conflitos entre classes do Tailwind de forma inteligente (por exemplo, quando um componente pai tenta sobrescrever uma cor definida internamente).
import { twMerge } from "tailwind-merge";
import { clsx, type ClassValue } from "clsx";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}Essa função cn se tornou um padrão de mercado, popularizado pelo shadcn/ui, e é extremamente útil para componentes que aceitam className como prop.
Conclusão
React com Tailwind CSS v4 é uma das combinações mais produtivas disponíveis hoje para desenvolvimento frontend. A nova versão eliminou boa parte da complexidade de configuração, os builds ficaram dramaticamente mais rápidos com a engine em Rust, e a experiência de desenvolvimento com a diretiva @theme é superior ao antigo arquivo de configuração JavaScript.
O ponto central que quero deixar é: não trate Tailwind apenas como "classes utilitárias no HTML". Quando usado corretamente com React, ele se torna parte integral da arquitetura de componentes. Extraia componentes com props tipadas, use mapas de variantes para estilos condicionais, e mantenha seus design tokens centralizados no @theme.
Se você já trabalha com Next.js, a integração fica ainda mais natural. Recomendo conferir o post sobre Server Actions do Next.js 15 para entender como combinar essa stack completa em aplicações de produção.
A curva de aprendizado inicial existe, principalmente para quem vem de CSS Modules ou Styled Components. Mas após as primeiras semanas, a velocidade de iteração compensa amplamente o investimento.
Termos do Glossário
Guias Relacionados
Explore mais guias técnicos
Mais de 100 guias práticos de integração entre frameworks, ferramentas e plataformas.
Ver todos os guias