Card

Pronto

Container de uma unidade de informação coesa. Sombra sutil, radius generoso, padding interno generoso. Reusa em KPIs, processos, cards do Kanban.

Usar quando

Agrupar campos relacionados (KPI, processo, contato). Item em grid. Surface elevada para destaque visual.

Não usar quando

Container sem identidade própria (use <div>). Quando viola hierarquia (cards aninhados em cards são suspeitos).

Variantes

Padrão (elevation 1 — sombra sutil)

Conteúdo do card. Padding 20px, sombra sm. Elevation 1 é default.

Elevation 2 (mais destaque, hover de lista)

Sombra md. Use para destaque ou em hover de cards interativos.

Outline (sem sombra, com borda)

Outline (sem sombra). Use em listas densas ou contextos minimalistas.

Interactive (hover lift)
Com header e ações

Últimas petições

Conteúdo do card com header padrão.

Stat card (KPI)

Tarefas concluídas

247+18%

Pontos do mês

1.840+5%

Atrasados

3−2

Microinterações

MicrointeraçãoDisparada porComportamentoTiming
Hover lift (interactive)mouseentertranslateY -2px + sombra cresce sm→md200ms cubic-bezier(0.32,0.72,0,1)
Click pressmousedown (interactive)translateY 0 + scale 0.99100ms ease
Focus ringfocus-visible (interactive)outline 2px borderInk com offset 2pxinstant
Stat delta pulsevalue mudaDelta scale 1→1.1→1300ms ease

Acessibilidade

Acessibilidade — checklist

ARIA esperado
  • Card interativo: <a> ou <button> wrapping content
  • Card de display: <article> ou <div role="region" aria-labelledby>
  • Header: <h3> dentro do card
  • Ações no header: <button aria-label="Mais opções" />
Notas
  • Cards aninhados são red flag — repensar hierarquia.
  • Card interativo precisa ser <a> ou <button> (não <div onClick>).
  • Padding generoso (24px) > apertado (12px). Branco é a feature mais cara.
  • Elevation 1 default, 2 só pra destacar (hero, modal).

Código

'use client';
import { T, SP, RADIUS } from '@/lib/tokens';

interface CardProps {
  children: React.ReactNode;
  elevation?: 1 | 2 | 3;
  outline?: boolean;
  interactive?: boolean;
  padding?: number;
}

export function Card({ children, elevation = 1, outline, interactive, padding = SP[5] }: CardProps) {
  const shadow = outline ? 'none' : elevation === 1 ? T.shadowSm : elevation === 2 ? T.shadowMd : T.shadowLg;
  return (
    <div
      style={{
        background: T.surface,
        borderRadius: RADIUS.lg,
        boxShadow: shadow,
        border: outline ? `1px solid ${T.border}` : 'none',
        padding,
        cursor: interactive ? 'pointer' : 'default',
        transition: interactive ? 'all 200ms cubic-bezier(0.32,0.72,0,1)' : 'none',
      }}
      onMouseEnter={(e) => {
        if (interactive) {
          e.currentTarget.style.transform = 'translateY(-2px)';
          e.currentTarget.style.boxShadow = T.shadowMd;
        }
      }}
      onMouseLeave={(e) => {
        if (interactive) {
          e.currentTarget.style.transform = 'translateY(0)';
          e.currentTarget.style.boxShadow = shadow;
        }
      }}
    >
      {children}
    </div>
  );
}

Regras

Faça

  • Padding interno generoso (SP[5] ou SP[6] = 20-24px).
  • Radius lg (18px) — Apple usa 14-22px.
  • Elevation 1 default; 2 só para destaque; 3 só para overlay.
  • Interactive card precisa ser <a> ou <button> (semântica + a11y).
  • Header opcional: <h3> + ações à direita (kebab).

Não faça

  • Não aninhe cards (cards dentro de cards = hierarquia quebrada).
  • Não use border + sombra simultâneos (pollui visual).
  • Não force radius pequeno (sm = 6px é só para chips/badges).
  • Não tenha mais que 3 níveis de elevation na mesma view.
  • Não use cor brass como background de card (brass é sinal).

Tokens usados

TokenValorPapel
T.surface#FFFFFFbackground
T.shadowSm0 1px 2px ...elevation 1
T.shadowMd0 4px 14px ...elevation 2 + hover
T.shadowLg0 16px 40px ...elevation 3
T.border#E3DFD2borda outline
RADIUS.lg18borderRadius
SP[5]20padding interno