Balance Card

Saldo projetado. Peça-chave do core da tese (UC-02).

Abrir no Storybook ↗

Quando usar

  • Quando o produto precisa exibir saldo projetado ou atual num horizonte temporal com delta comparativo opcional — a peça central do fluxo de viabilidade (UC-02).
  • Dashboard financeiro como card principal de orientação: o usuário abre o app e precisa saber de onde está antes de qualquer ação.
  • Primeiro bloco da resposta de viabilidade — UC-02: "Dá pra fazer, mas aperta. Saldo projetado em 15/dez: R$ 1.200 após viagem e compromissos fixos."
  • Hero de saldo quando a tela tem saldo como dado dominante (ex.: tela de planejamento de viagem, simulação de antecipação).

Quando não usar

  • Para detalhar a composição do saldo (quais lançamentos, quais categorias) — usar listagem de lançamentos. O Balance Card entrega o número; a listagem explica de onde ele veio.
  • Para valor único sem contexto temporal (ex.: "você tem R$ 500 na conta") — usar texto simples em serif dentro de um contexto que já tem estrutura. O Balance Card sem horizon perde o dado que justifica a peça.
  • Em superfícies de dívida, inadimplência ou risco alto — nessas cenas o card é substituído por bloco funcional de estado crítico em V-A puro. Nenhum saldo projetado alegre quando a situação é grave.

Anatomia

Cinco slots, dois obrigatórios (kicker e value). Os slots horizon, delta e caption são opcionais mas altamente recomendados — sem horizon o valor fica sem âncora temporal.

Decisão de markup: composição de classes. O Balance Card usaclassName="peppe-card peppe-balance-card" — compõe a classe base do Card com o modificador semântico do Balance Card. Essa abordagem herda o CSS do primitivo (raised, --radius-xl quando é card-mãe, sombra, border gradient) e adiciona apenas o layout e a tipografia específicos. Evita duplicação de tokens de superfície e mantém claro que o Balance Card é um Card com semântica adicional — alinhado com o princípio de economia de vocabulário (components.md §7.1).

Raio padrão: --radius-xl (32px). O Balance Card é card-mãe do dashboard na maioria das cenas — portanto usa o raio mais aberto da escala, sinalizando hierarquia. Quando encaixado dentro de outro card ou usada como hero de seção, o modificador peppe-card--hero (--radius-lg, 24px) pode ser aplicado junto.

Variantes

Uma variante por enquanto. O Balance Card v1 tem variante única — a composition base com os cinco slots. Variações de layout (horizontal value + delta; valor em faixa estreita; modo compacto para mobile) virão com surface templates no ciclo seguinte, quando o padrão de uso real em produto estiver validado.

SALDO PROJETADO

R$ 1.200

em 15/dez

+R$ 230 vs. média

após viagem e compromissos fixos

Variantes por canal. app: card visual com value em display serif (default). voz: locução linear ("Saldo projetado em 15 de dezembro: mil e duzentos reais; duzentos e trinta acima da média"). whatsapp: bloco com kicker em caps + value em texto reforçado + horizon e delta em linhas separadas. e-mail: card HTML fallback com tabela inline e fonte sistema em caso de fallback sem Fraunces. sms: formato ultra-compacto em 1 linha — "SALDO 15/dez: R$ 1.200 (+R$ 230)".

Estados

default

SALDO PROJETADO

R$ 1.200

em 15/dez

+R$ 230 vs. média

após viagem e compromissos fixos

Cópia canônica do UC-02. Kicker + value em display serif + horizon + Chip success + caption do Peppe.

loading

Kicker e horizon em linhas xs/sm; value vira bloco alto preservando a dimensão do display serif. Caption em linha md. Sem texto — leitor de tela recebe aria-busy="true".

erro

Bloco funcional com ícone de erro, texto canônico e ação de retry. role="alert" para leitores de tela. Escala para SystemError template quando necessário.

vazio

Sem histórico ainda. Conecta sua conta pra eu acompanhar.

EmptyState propositivo. A tela vazia vira convite. Sem lamento, sem desculpa — segue voice-and-tone.md §3.1 (estado vazio: leve e propositivo).

Tokens

O Balance Card herda os tokens de superfície do Card (raised) e adiciona os papéis tipográficos específicos dos seus slots.

TokenValorPapel
Herdados do Card
--color-surface-raised-top/bottom#F7F7F7 → #FBFBFBGradiente de fundo raised
--border-raised-top/bottom#FCFCFC → #F8F8F8Border gradient direcional
--shadow-raisedSombra direcional canto sup-esq
--radius-xl32pxRaio card-mãe (padrão do Balance Card)
--space-3232pxPadding interno (herda de mother)
Slots do Balance Card
--type-family-sansInstrument Sanskicker, horizon, caption
--type-family-serifFrauncesvalue — ≥ 32px (limiar: visual-language.md §4.4)
--type-size-xs12pxkicker — caps medium tracking-loose
--type-size-display64pxvalue — serif tracking-tight
--type-size-sm14pxhorizon, caption
--type-tracking-loose0.08emkicker (uppercase tracking)
--type-tracking-tight-0.02emvalue (display serif)
--color-ink-primary#171717value — contraste AA sobre raised
--color-ink-tertiary#A1A1A1kicker
--color-ink-secondary#6B6B6Bhorizon, caption
--space-44pxgap kicker → value (ajuste óptico)
--space-88pxgap value → horizon, gap horizon → delta
--space-1616pxgap delta → caption
Delta — Chip herdado
--color-functional-success / -tint#37A35A / #E8F2ECdelta positivo
--color-functional-alert / -tint#D4A017 / #F6EEDAdelta negativo

Nota sobre serif no value. O token --type-family-serif (Fraunces, placeholder de sabor retrô-70s) é aplicado ao .peppe-balance-card__value porque o tamanho resultante é 64px — bem acima do limiar de 32px definido em visual-language.md §4.4. Em breakpoints menores, --type-size-display cai para 56px (tablet+mobile) — ainda acima do limiar. A serif nunca desce de 32px neste componente.

Conteúdo

kicker é a etiqueta de categoria — "SALDO PROJETADO" ou "SALDO ATUAL". Caps, máximo 2–3 palavras. Zero pontuação final. Nunca "SALDO DO MÊS DE DEZEMBRO DE 2026" — o kicker posiciona, não descreve.

value é sempre BRL com separador de milhar e decimais opcionais: "R$ 1.200" (sem decimais quando o arredondamento é explícito) ou "R$ 1.200,00" (quando a precisão é relevante). Ponto como separador de milhar, vírgula como decimal — locale PT-BR. Nunca "1200" sem formatação; nunca "BRL 1200".

horizon é o âncora temporal — curto e direto: "em 15/dez", "hoje", "fim do mês", "em 30 dias". Sem "no dia quinze de dezembro". O usuário lê em trânsito.

delta é o Chip de comparativo com tone coerente: success para delta positivo (acima da média, crescimento), alert para delta negativo (abaixo, queda). A cor do Chip não é decorativa — é sinal funcional. Exemplos: "+R$ 230 vs. média", "−R$ 140 vs. mês anterior".

caption é o Peppe amarrando o contexto — a frase que justifica o número: "após viagem e compromissos fixos", "incluindo parcelas de dezembro". Máximo 1 linha. Referência canônica: content-design/examples.md §6"Dá pra fazer, mas aperta. Saldo projetado em 15/dez: R$ 1.200 após viagem e compromissos fixos."

Fallback SMS: "SALDO 15/dez: R$ 1.200" — valor + horizonte, sem formatação rica. Máximo 160 caracteres.

Acessibilidade

  • aria-label composto no container. O <article> recebe aria-label que sumariza todos os slots presentes: "saldo projetado em 15/dez: R$ 1.200. +R$ 230 vs. média. Após viagem e compromissos fixos." — o leitor de tela lê o bloco inteiro sem precisar navegar pelos filhos.
  • Valor em ink-primary sobre raised. #171717 sobre #F7F7F7 — contraste ~17:1, passa WCAG AAA. A serif em 64px não tem risco de contraste mesmo em pesos leves.
  • Estado loading: aria-busy="true" no container. Os blocos skeleton não têm texto — o leitor de tela não anuncia os placeholders. Dimensões preservadas para evitar layout shift.
  • Estado erro: role="alert" no .peppe-card__error-block para anúncio imediato pelo leitor de tela.
  • Estado vazio: texto propositivo lido naturalmente. Sem role="alert" — estado vazio não é urgente.
  • Chip delta: o .peppe-chip no slot delta recebe aria-label explícito quando o texto não é autoexplicativo (ex.: aria-label="+R$ 230 vs. média mensal de delivery").
  • Fallback de canal — voz: SDUI entrega kicker + value + horizon + caption. Delta é omitido (o sinal de cor não tem equivalente auditivo). Horizon é lido como "em quinze de dezembro".

Do / Don't

Do

SALDO PROJETADO

R$ 1.200

em 15/dez

Value em display serif — carrega voz. O usuário lê como "mensagem do Peppe", não como tabela de dados.

Don't

SALDO PROJETADO

R$ 1.200

em 15/dez

Value em sans bold cinza — parece tabela de extrato, perde a voz. A hierarquia tipográfica que diferencia o Balance Card de uma linha de listagem desaparece.

Do

+R$ 230 vs. média−R$ 140 vs. média

Chip com tone coerente — success para positivo, alert para negativo. A cor é sinal funcional: o usuário entende o estado antes de ler o número.

Don't

+R$ 230 vs. média

Delta com cor inventada fora das funcionais — azul, roxo, laranja de marca. A cor decorativa dilui a hierarquia: quando tudo é colorido, nada é sinal. Anti-padrão visual #5.

Do

Caption curta e útil — amarra o contexto que o value não entrega: "após viagem e compromissos fixos". O usuário entende por que o saldo é esse número. Uma informação por caption.

Don't

Caption decorativa sem dado: "seu saldo tá aqui!" ou "veja abaixo os detalhes". Não amarra, não informa — é filler. Anti-padrão de conteúdo #1 — Filler de abertura e #8 — Pleasantries.

Componentes relacionados

  • Card — scaffold do Balance Card. O Balance Card é instância nomeada de Card; herda superfície raised, sombra, border gradient e raio.
  • Chip — usado no slot delta com tone success (positivo) ou alert (negativo). Nunca criar chip com cor fora das funcionais.
  • Section Hero — quando o Balance Card é o hero da tela, o Section Hero pode preceder com eyebrow + title de saudação contextual. O Section Hero não contém o Balance Card — os dois vivem em sequência no container pai.
  • Commitment Card — composition irmã. Segue o Balance Card na hierarquia do dashboard — o saldo projetado (aqui) contextualiza os compromissos (lá).

Markup de referência

HTML namespaced copiável. Slots opcionais podem ser omitidos sem quebrar o componente — apenas o kicker e o value são obrigatórios.

Decisão de arquitetura: o Balance Card usa composição de classes — peppe-card peppe-balance-card. A classe peppe-card entrega a superfície raised (gradiente, border gradient, sombra, overflow hidden); a classe peppe-balance-card entrega o layout interno dos slots e os papéis tipográficos específicos. Nenhuma duplicação de CSS de superfície.

<!-- Balance Card — variante completa (todos os slots) --> <article className="peppe-card peppe-balance-card" aria-label="saldo projetado em 15/dez: R$ 1.200. +R$ 230 vs. média. Após viagem e compromissos fixos."> <!-- kicker: obrigatório — SALDO PROJETADO ou SALDO ATUAL --> <span className="peppe-balance-card__kicker">SALDO PROJETADO</span> <!-- value: obrigatório — BRL locale PT-BR, serif display --> <p className="peppe-balance-card__value">R$ 1.200</p> <!-- horizon: opcional mas recomendado — âncora temporal --> <p className="peppe-balance-card__horizon">em 15/dez</p> <!-- delta: opcional — Chip com tone success (positivo) ou alert (negativo) --> <div className="peppe-balance-card__delta"> <span className="peppe-chip peppe-chip--success" aria-label="+R$ 230 vs. média">+R$ 230 vs. média</span> </div> <!-- caption: opcional — Peppe amarrando o contexto (1 linha máx.) --> <p className="peppe-balance-card__caption">após viagem e compromissos fixos</p> </article> <!-- Balance Card — estado loading (skeleton) --> <article className="peppe-card peppe-balance-card peppe-balance-card--loading" aria-busy="true" aria-label="Carregando saldo"> <div className="peppe-card__skeleton-line peppe-card__skeleton-line--xs peppe-balance-card__skeleton-kicker"></div> <div className="peppe-balance-card__skeleton-value"></div> <div className="peppe-card__skeleton-line peppe-card__skeleton-line--sm peppe-balance-card__skeleton-horizon"></div> <div className="peppe-card__skeleton-line peppe-card__skeleton-line--md peppe-balance-card__skeleton-caption"></div> </article> <!-- Balance Card — estado erro --> <article className="peppe-card peppe-balance-card peppe-card--error" aria-label="Erro ao carregar saldo"> <div className="peppe-card__error-block" role="alert"> <svg className="peppe-card__error-icon" ...></svg> <p className="peppe-card__error-text"> Não consegui carregar o saldo agora. Tenta de novo? </p> <button className="peppe-button peppe-button--tertiary" type="button"> <span className="peppe-button__label">Tentar de novo</span> </button> </div> </article> <!-- Balance Card — estado vazio --> <article className="peppe-card peppe-balance-card peppe-card--empty" aria-label="Sem histórico de saldo"> <div className="peppe-card__empty-block"> <svg className="peppe-card__empty-icon" ...></svg> <p className="peppe-card__empty-text"> Sem histórico ainda. Conecta sua conta pra eu acompanhar. </p> </div> </article>