Ir para o conteúdo
Framework com Framework1675 palavras

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:

TYPESCRIPT
// Terminal: criando o projeto
// npm create vite@latest meu-projeto -- --template react-ts
// cd meu-projeto
// npm install

Instalando o Tailwind CSS v4

No Tailwind v4, a instalação ficou mais enxuta. Você precisa apenas do plugin para Vite:

TYPESCRIPT
// Terminal: instalando Tailwind CSS v4
// npm install tailwindcss @tailwindcss/vite

Em seguida, registre o plugin no arquivo vite.config.ts:

TYPESCRIPT
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:

TYPESCRIPT
/* 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.

TYPESCRIPT
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:

TYPESCRIPT
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).

TYPESCRIPT
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