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.
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.
@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.
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
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.

