Marcos N
← Voltar ao blog

Tailwind CSS

Tailwind CSS 4: como criar componentes responsivos e consistentes

Aprenda a organizar tokens, variantes e estados de interação no Tailwind CSS 4 com um card em React e uma grade responsiva de três colunas.

Marcos N6 min de leitura
Tailwind CSS 4: como criar componentes responsivos e consistentes

Para criar componentes consistentes com Tailwind CSS, defina valores compartilhados, escreva variantes com classes completas e trate responsividade, foco e movimento como partes do componente. A biblioteca oferece as ferramentas; a coerência depende das decisões que você repete.

Um problema comum aparece quando cada tela ganha um roxo diferente, um novo raio de borda e uma combinação própria de espaçamentos. Mesmo usando o mesmo framework, o site começa a parecer um conjunto de projetos separados.

Neste tutorial, vamos construir um card de artigo em React e TypeScript, com Tailwind CSS 4. A proposta combina design e código: tokens para a identidade visual, variantes explícitas e uma grade com uma, duas ou três colunas.

Antes de começar

O exemplo pressupõe um projeto React com Tailwind CSS 4 já integrado ao processo de build. Os arquivos TypeScript devem estar em uma pasta detectada pelo Tailwind. As diretivas de tema apresentadas aqui pertencem à versão 4; um projeto na versão 3 usa outra configuração.

O componente não depende de uma biblioteca de ícones nem de código executado a cada evento de mouse.

1. Defina os valores que pertencem ao projeto

No CSS global, declare apenas os valores que você quer compartilhar. O @theme permite criar tokens que geram utilitários, como bg-brand e rounded-card. Essa relação está descrita na documentação de tema do Tailwind.

globals.css
@import "tailwindcss";

@theme {
  --color-brand: #7545ff;
  --color-brand-strong: #5428b8;
  --color-surface: #f7f7f2;
  --color-ink: #171717;
  --radius-card: 1.5rem;
}

Um token não precisa existir para cada número da interface. Ele é especialmente útil quando representa uma decisão recorrente: a cor principal da marca, o fundo da página ou o arredondamento de uma família de componentes.

Antes de criar outro valor, confira se um existente atende ao contexto. O objetivo é reduzir diferenças acidentais sem impedir exceções justificadas.

2. Escreva um card com variantes explícitas

O card abaixo é um link inteiro. Ele tem apenas uma ação e não contém botões ou outros links aninhados. Isso simplifica o clique e a navegação por teclado.

ArticleCard.tsx
import type { ReactNode } from 'react';

type ArticleCardProps = {
  title: string;
  category: string;
  href: string;
  image?: string;
  children: ReactNode;
  tone?: 'light' | 'dark';
};

const tones = {
  light: {
    card: 'bg-white text-ink',
    category: 'text-brand-strong',
    description: 'text-neutral-600',
    action: 'bg-violet-50 text-brand-strong',
  },
  dark: {
    card: 'bg-neutral-950 text-white',
    category: 'text-violet-300',
    description: 'text-neutral-300',
    action: 'bg-violet-950 text-violet-200',
  },
} as const;

export function ArticleCard({
  title, category, href, image, children, tone = 'light',
}: ArticleCardProps) {
  const colors = tones[tone];

  return (
    <a href={href} className={[
      'group flex min-w-0 flex-col overflow-hidden rounded-card',
      'focus-visible:outline-2 focus-visible:outline-offset-4',
      'focus-visible:outline-brand',
      colors.card,
    ].join(' ')}>
      {image && (
        <div className="aspect-[1.7] overflow-hidden bg-neutral-200">
          <img src={image} alt="" loading="lazy"
            className="h-full w-full object-cover motion-safe:transition-transform motion-safe:duration-500 motion-safe:group-hover:scale-[1.035]" />
        </div>
      )}
      <div className="flex flex-1 flex-col p-6">
        <p className={`text-xs font-medium uppercase tracking-widest ${colors.category}`}>
          {category}
        </p>
        <h2 className="mt-4 text-2xl font-semibold leading-tight tracking-tight">
          {title}
        </h2>
        <p className={`mt-3 text-base leading-relaxed ${colors.description}`}>
          {children}
        </p>
        <div className="mt-auto flex items-center justify-between gap-4 pt-8">
          <span className="text-sm font-medium">Ler artigo</span>
          <span aria-hidden="true"
            className={`grid size-10 place-items-center rounded-full ${colors.action}`}>
            ↗
          </span>
        </div>
      </div>
    </a>
  );
}

As variantes incluem todas as cores necessárias ao contexto. Trocar apenas o fundo pode deixar o texto com pouco contraste. É por isso que o mapeamento reúne fundo, categoria, descrição e ação.

A imagem usa texto alternativo vazio porque, neste exemplo, é uma capa decorativa que acompanha o título do mesmo link. Se a imagem carregar informação que o texto não comunica, reveja essa decisão.

3. Monte uma grade que cresce com o conteúdo

Articles.tsx
import { ArticleCard } from './ArticleCard';

export function Articles() {
  return (
    <main className="min-h-screen bg-surface px-6 py-16">
      <section className="mx-auto max-w-6xl" aria-labelledby="articles-title">
        <h1 id="articles-title" className="mb-8 text-4xl font-semibold text-ink">
          Artigos sobre interfaces
        </h1>
        <div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
          <ArticleCard title="Formulários acessíveis" category="React"
            href="/blog/formulario-acessivel-react-validacao">
            Rótulos, mensagens e foco para orientar o preenchimento.
          </ArticleCard>
          <ArticleCard title="Busca sem respostas antigas" category="TypeScript"
            href="/blog/busca-react-debounce-abortcontroller">
            Debounce e cancelamento de requisições na prática.
          </ArticleCard>
          <ArticleCard title="Movimento no scroll" category="JavaScript" tone="dark"
            href="/blog/animacao-scroll-intersection-observer">
            Uma animação de entrada com alternativa sem movimento.
          </ArticleCard>
        </div>
      </section>
    </main>
  );
}

As classes sem prefixo definem a base. md: e lg: aplicam mudanças a partir dos respectivos breakpoints, e não a categorias fixas de aparelho. Com a configuração padrão, a grade passa de uma para duas e depois para três colunas. Um quarto item ocupa a próxima linha. Veja a referência de responsividade.

O flex-1 permite que a área textual ocupe o espaço disponível; mt-auto empurra a ação para o fim dessa área. Assim, títulos de tamanhos diferentes não obrigam a definir uma altura rígida para o texto.

4. Evite montar nomes de classes com pedaços de strings

Uma interpolação como bg-${cor}-500 pode não produzir o CSS esperado. O processo de detecção precisa encontrar os nomes completos das classes nos arquivos de origem. Um mapa com strings completas, como tones, torna as possibilidades explícitas. A documentação de detecção de classes explica esse limite.

Valores realmente dinâmicos, como uma porcentagem recebida de uma API, podem ser enviados por uma variável CSS ou estilo inline validado. Isso é diferente de transformar uma propriedade arbitrária em um nome de utilitário.

5. Quando extrair um componente?

Extraia quando houver um conjunto de comportamento, estrutura e decisões visuais que precisa continuar consistente. Um card de artigo é um bom candidato porque tem uma função definida.

Não é necessário criar componentes para cada combinação de duas classes. Também não é obrigatório transformar toda repetição em @apply. A escolha deve facilitar a manutenção: uma alteração de layout deveria ter um lugar compreensível para acontecer.

CSS próprio continua útil para seletores de conteúdo editorial, animações complexas ou integração com bibliotecas. Tailwind não elimina a necessidade de entender cascata, herança, layout e acessibilidade.

Como revisar o resultado

Teste um título curto e outro longo, quatro cards em vez de três, navegação com Tab e preferência por movimento reduzido. Reduza a largura sem se limitar aos tamanhos dos breakpoints e verifique se o conteúdo continua legível entre eles.

A grade é responsiva quando acompanha o conteúdo sem perder sua função, não apenas quando muda de quantidade de colunas.

Perguntas frequentes

Tailwind deixa todos os sites com a mesma aparência?

Não. As classes fornecem ferramentas. Tipografia, composição, conteúdo e regras de identidade visual definem o resultado. Usar sempre os mesmos exemplos prontos é uma escolha de projeto.

Preciso usar valores arbitrários em todo lugar?

Não. Use os valores existentes quando servirem ao projeto. Valores arbitrários são úteis para necessidades específicas; os recorrentes podem se tornar tokens.

Posso usar esse card no Next.js?

Sim. Adapte navegação e imagens aos componentes do projeto quando isso fizer sentido. O exemplo usa HTML e React para manter o comportamento independente do framework.

Para aprofundar a interação, leia como equilibrar animação, performance e acessibilidade. E veja nos projetos do portfólio como conecto essas decisões ao desenvolvimento de interfaces.