Card

Envelope de superfície raised com padding consistente. Scaffold das compositions.

Abrir no Storybook ↗

Quando usar

  • Para agrupar conteúdo de um mesmo tópico: compromisso financeiro, saldo projetado, grupo de ações rápidas, aviso contextual.
  • Quando o conteúdo precisa pousar visualmente sobre a base da interface — distinguindo-se do fundo por elevação, não por cor.
  • Como container de compositions: Balance Card, Commitment Card e Quick Actions Card são instâncias nomeadas de Card.

Quando não usar

  • Para separação hierárquica simples entre blocos de conteúdo — usar Hairline ou espaçamento adicional.
  • Para envolver um único elemento inline (botão isolado, chip, ícone) sem contexto de agrupamento.
  • Card dentro de Card dentro de Card — nesting além de dois níveis é anti-padrão. O terceiro nível fragmenta a hierarquia e sinaliza que a cena deveria ser re-arquitetada.

Anatomia

O Card tem três regiões — header e actions são opcionais; body é o único slot obrigatório.

Slots do componente:

  • header (opcional) — região superior com kicker e/ou title. Separada do body por hairline quando presente.
  • kicker — string curta em caps, tracking-loose, --color-ink-tertiary. Funciona como etiqueta de categoria ou contexto.
  • title — título do card. Sans medium --type-size-lg (20px) no default; serif em cards de destaque (size=lg) onde o tamanho atinge o limiar de 32px+.
  • body — área livre (slot children). Aceita texto, Chip, Icon, Hairline, e Card aninhado (máximo um nível abaixo).
  • actions (opcional) — lista de 0 a 3 Button. Separado do body por hairline quando presente. Com 4 ou mais ações, a cena deve ser re-arquitetada.

Variantes

VarianteRegistroSombraUso
raised (default)V-A puro--shadow-raisedCards de produto em dashboard, listagem, onboarding.
floatingV-A puro--shadow-floatingOverlay, modal, popover — contextos em que o card precisa se destacar do conteúdo por baixo.
raised-accent (V-B)V-B contido--shadow-raisedPendente v1 — depende do lote 3 V-B (Story B.4). Reservado para celebração de meta e categorias com acento retrofuturista. O contrato surface="raised-accent" está declarado e a classe é aplicada, mas renderiza sem estilo diferenciado (herda raised/V-A) até o ui-designer entregar o token V-B.

Eixos de escala — radius e size são props independentes (ortogonais):

PropValoresEfeito
radiusxl (default) · lg · mdRaio do envelope. xl = 32px, lg = 24px, md = 16px.
sizemd (default) · lgPadding interno. md = --space-24 (24px), lg = --space-32 (32px).

Combos nomeados. Os antigos modificadores monolíticos --hero e --mother foram descontinuados (misturavam raio + padding num só nome, e --hero colidia semanticamente com o componente Section Hero). Em vez de modificadores, são combinações de props: card de destaque = radius="lg" size="lg"; card-mãe do dashboard = radius="xl" size="lg" (raio maior sinaliza hierarquia: mãe mais aberta, filhos contidos). Decisão D-Card-1/D-Card-2 do orquestrador (2026-05-28).

Variantes por canal. app: card visual (default — surface-raised, sombra direcional). whatsapp: bloco de mensagem — header em negrito + body em prosa + actions como quick-replies (0–3 botões colapsam em respostas rápidas). voz: locução linear — kicker + title viram abertura falada, body vira parágrafo único, actions viram pergunta de ação ("quer que eu faça isso?"). e-mail: card HTML com as mesmas três áreas, CSS inline de fallback. sms: colapso em 1–2 frases + keyword de ação.

CategoriaTítulo do card

Conteúdo do body: texto, Chip, Icon ou Card aninhado.

raised · default
OverlayFloating

Usado em modal, popover ou drawer.

floating · overlay

raised-accent V-B — pendente v1.Depende do lote 3 V-B (Story B.4). Contratosurface="raised-accent" reservado; CSS de superfície em rodada futura.

raised-accent · V-B pendente

Estados

CompromissoCondomínio

Vence em 5 dias. R$ 380.

default
loading (skeleton)
error
Nenhum item aqui ainda. Manda um pra eu anotar.
vazio

Sobre error e vazio. O Card base entrega apenas a sinalização textual via errorMessage / emptyMessage (princípio components.md §0.3 — componente não hardcoda copy). Ícone, botão de "tentar de novo" e qualquer ação de recuperação são responsabilidade do host que usa o Card. Acessibilidade: o estado error usa role="alert" (anúncio imediato — o usuário precisa saber que não carregou); o estado vazio usa aria-live="polite" (não é urgente).

Tokens aplicáveis

TokenValorPapel
--color-surface-raised-top#F7F7F7Fundo — topo do gradiente vertical
--color-surface-raised-bottom#FBFBFBFundo — base do gradiente vertical
--border-raised-top#FCFCFCBorder gradient — topo iluminado
--border-raised-bottom#F8F8F8Border gradient — base sombreada
--shadow-raisedSombra default (variante raised)
--shadow-floatingSombra overlay (variante floating)
--radius-md16pxRaio default
--radius-lg24pxRaio de card de destaque (radius=lg)
--radius-xl32pxRaio default e de card-mãe (radius=xl)
--space-2424pxPadding interno padrão
--space-2424pxPadding interno default (size=md)
--space-3232pxPadding interno de cards de destaque (size=lg)
--color-surface-divider#ECECECHairline entre header/body/actions
--color-ink-primary#171717Texto de title e body principal
--color-ink-secondary#6B6B6BTexto de body secundário
--color-ink-tertiary#A1A1A1Kicker (caps, tracking-loose)
--type-family-sansInstrument SansKicker, body, actions
--type-family-serifFrauncesTitle em cards de destaque (size=lg) quando ≥ 32px
--color-functional-error#C43D3DÍcone e texto no estado error

Nota sobre serif no title. O token --type-family-serif só é aplicado ao peppe-card__title quando o tamanho resultante atinge o limiar de 32px+. Em cards padrão (size=md) e de destaque (radius=lg size=lg), o title usa --type-size-lg (20px) — sans. Em cards-mãe (radius=xl size=lg) com --type-size-xl (40px desktop / 32px tablet+mobile), serif é autorizada. Ver Tipografia §4.4.

Conteúdo

  • Header: kicker é caps, tracking-loose, --color-ink-tertiary. Funciona como etiqueta da cena — "COMPROMISSO", "SALDO", "AVISO". Zero pontuação.
  • Header: title segue a regra de sans/serif por tamanho. Cards padrão e de destaque (size=lg) usam sans; card-mãe (radius=xl size=lg) com title ≥ 32px usa serif. Hierarquia de heading deve ser mantida (h2, h3 conforme a hierarquia da página).
  • Body é denso por princípio. Cada parágrafo carrega dado, ação ou pergunta. Ver Voz & Tom §4.3 — densidade e extensão.
  • Actions: 0 a 3 ações. Ações agrupadas no slot actions, nunca dispersas dentro do body. Com 4 ou mais ações, a cena deve ser outra.
  • Ações em ordem de ênfase: primary ou secondary à esquerda; tertiary à direita. Nunca mais de uma secondary/primary por slot de ações.

Acessibilidade

  • Card não-interativo: usar elemento <article> com hierarquia de headings interna (h2/h3 conforme o nível na página). O leitor de tela navega pelos headings sem precisar de aria-label no container.
  • Card clicável inteiro (role="button" ou âncora): o container recebe aria-label sumarizando o conteúdo principal (ex.: aria-label="Condomínio — R$ 380 — vence em 5 dias") e recebe foco com ring visível.
  • Estado loading: o container recebe aria-busy="true". O skeleton não precisa de texto — dimensões são preservadas para que o layout não trepide.
  • Estado error: o bloco de erro usa role="alert" para que leitores de tela anunciem imediatamente.
  • Estado vazio: texto propositivo curto dentro do card — lido naturalmente pelo leitor de tela. Sem role="alert" (estado vazio não é urgente).
  • Contraste: ink primary sobre superfície raised atende WCAG AA. O gerador valida antes de ir ao ar.

Do / Don't

Do

Buscar lançamento

Um raio por card; inputs internos no degrau abaixo da escala. Card no radius=xl contém campo com raio-md — ritmo coerente.

Don't

Buscar lançamento

Raios que não rimam no mesmo card — card no radius=lg (24px), input no radius-sm (8px). O degrau é grande demais; os cantos não conversam.

Do

Ações agrupadas no slot actions do card. O usuário sabe onde encontrar as ações — sempre na borda inferior, após o conteúdo.

Don't

Ações dispersas pelo body do card — botão no meio do texto, link inline ao lado do dado, outro botão no canto. Sem localização previsível.

Do

--floating em modal ou popover real — onde o card precisa se destacar visualmente de um backdrop. A sombra longa é o sinal de que o conteúdo veio à frente.

Don't

--floating em card estático no dashboard vira ruído de hierarquia. A sombra longa gera percepção de modal sem modal — o usuário não sabe se o card é interativo ou flutuante.

Componentes relacionados

  • Balance Card — instância nomeada de Card. Saldo projetado em serif + delta funcional.
  • Commitment Card — instância nomeada de Card. Compromisso financeiro com data, valor e ação de pagamento.
  • Quick Actions Card — instância nomeada de Card. Grid de ações rápidas.
  • Hairline — alternativa ao nesting quando a separação precisa ser leve, sem criar hierarquia de envelope.

Uso

Importa o <Card> do @peppe/design-system. Header estruturado via prop header (kicker + title); body via children; ações via actions. A variante raised-accent (V-B) renderiza sem estilo diferenciado até a Story B.4 entregar o token.

import { Card, Button } from "@peppe/design-system";

// Card raised default — header + body + actions
<Card
  header={{ kicker: "Compromisso", title: "Condomínio" }}
  actions={
    <>
      <Button emphasis="secondary" size="md">Pagar</Button>
      <Button emphasis="tertiary" size="md">Ver depois</Button>
    </>
  }
>
  <p>Vence em 5 dias. R$ 380.</p>
</Card>

// Card floating (overlay / modal / popover)
<Card elevation="floating" header={{ kicker: "Detalhe", title: "Condomínio — abr" }}>
  <p>Vence 30/abr. R$ 380. Juros a partir de 01/mai.</p>
</Card>

// Card de destaque — combo radius=lg size=lg (era "--hero")
<Card radius="lg" size="lg">
  <p>Melhor mês em delivery desde que a gente começou. R$ 230 a menos.</p>
</Card>

// Card-mãe do dashboard — combo radius=xl size=lg (era "--mother")
<Card radius="xl" size="lg" header={{ kicker: "Saldo", title: "Projetado em 15/mai" }}>
  {/* Cards filhos com radius=lg, um degrau abaixo */}
  <Card radius="lg">…</Card>
</Card>

// Estados — copy vem do host (errorMessage/emptyMessage); ícone e retry
// são responsabilidade do host, não do Card base.
<Card state="loading" />
<Card state="error" errorMessage="Não consegui carregar esse bloco. Tenta de novo?" />
<Card state="empty" emptyMessage="Nenhum item aqui ainda. Manda um pra eu anotar." />

// Card clicável inteiro — onClick presente exige aria-label (tipo discriminado)
<Card onClick={abrir} aria-label="Condomínio — R$ 380 — vence em 5 dias"
      header={{ kicker: "Compromisso", title: "Condomínio" }}>
  <p>Vence em 5 dias. R$ 380.</p>
</Card>

O CSS do Card viaja co-localizado com o componente em @peppe/design-system — não há mais componente-card.css de página (deletado em Epic #517 S8).