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 CardeQuick Actions Cardsão instâncias nomeadas de Card.
Quando não usar
- Para separação hierárquica simples entre blocos de conteúdo — usar
Hairlineou 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
| Variante | Registro | Sombra | Uso |
|---|---|---|---|
raised (default) | V-A puro | --shadow-raised | Cards de produto em dashboard, listagem, onboarding. |
floating | V-A puro | --shadow-floating | Overlay, modal, popover — contextos em que o card precisa se destacar do conteúdo por baixo. |
raised-accent (V-B) | V-B contido | --shadow-raised | Pendente 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):
| Prop | Valores | Efeito |
|---|---|---|
radius | xl (default) · lg · md | Raio do envelope. xl = 32px, lg = 24px, md = 16px. |
size | md (default) · lg | Padding 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.
Conteúdo do body: texto, Chip, Icon ou Card aninhado.
Usado em modal, popover ou drawer.
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.
Estados
Vence em 5 dias. R$ 380.
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
| Token | Valor | Papel |
|---|---|---|
--color-surface-raised-top | #F7F7F7 | Fundo — topo do gradiente vertical |
--color-surface-raised-bottom | #FBFBFB | Fundo — base do gradiente vertical |
--border-raised-top | #FCFCFC | Border gradient — topo iluminado |
--border-raised-bottom | #F8F8F8 | Border gradient — base sombreada |
--shadow-raised | — | Sombra default (variante raised) |
--shadow-floating | — | Sombra overlay (variante floating) |
--radius-md | 16px | Raio default |
--radius-lg | 24px | Raio de card de destaque (radius=lg) |
--radius-xl | 32px | Raio default e de card-mãe (radius=xl) |
--space-24 | 24px | Padding interno padrão |
--space-24 | 24px | Padding interno default (size=md) |
--space-32 | 32px | Padding interno de cards de destaque (size=lg) |
--color-surface-divider | #ECECEC | Hairline entre header/body/actions |
--color-ink-primary | #171717 | Texto de title e body principal |
--color-ink-secondary | #6B6B6B | Texto de body secundário |
--color-ink-tertiary | #A1A1A1 | Kicker (caps, tracking-loose) |
--type-family-sans | Instrument Sans | Kicker, body, actions |
--type-family-serif | Fraunces | Title 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,h3conforme 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/h3conforme o nível na página). O leitor de tela navega pelos headings sem precisar dearia-labelno container. - Card clicável inteiro (
role="button"ou âncora): o container recebearia-labelsumarizando 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
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
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).