Pular para conteúdo

Design System — Tokens (fonte única da verdade)

Reconciliado em 2026-07-25 a partir de impeccable.md (intenção) e do tema MUI em apps/web/src/contexts/color-mode/ (implementado), que divergiam. Estas são as decisões definitivas — o app e o Figma devem convergir para cá.

Onde os valores vivem em código (spec 0017): as paletas e os tokens theme.garantora.* estão em apps/web/src/design-system/theme/palette.ts, e os mesmos valores em CSS ficam ao lado, em design-system/tokens/*.css. Este documento é a prosa canônica; o palette.ts é o que roda. Mudou aqui, mude nos dois — enquanto os três existirem, essa é a duplicação que sobrou, e é deliberada.

Até a 0017 havia um quarto lugar: o README.md do pacote @garantora/design-system, uma reconstrução paralela feita a partir dos SVGs do Figma. O pacote foi internalizado e o repositório, arquivado — esse rival deixou de existir.

Decisões da reconciliação

Tema Resolução
Papel das cores Azul = estrutura/ação primária · Teal = marca/acento · Âmbar = destaque/CTA (reflete o uso real no login)
Tipografia Onest (display/títulos) + Inter (corpo) — como o app carrega hoje
Neutros Tingidos na marca (azulado): dark #0F172A/#1E293B, texto light #1a1a2e/#5f6878

1. Cores

Marca (semântica)

Token Papel Light Dark (contraste)
brand.structure ação primária, nav ativa, botões principais #005577 #4DA6CC
brand.structure-hover hover do primário #00445C #3D95BB
brand.accent identidade, links, acentos #008B8B #33B3B3
brand.accent-hover hover do acento #007A7A #2CA0A0
brand.highlight CTA de conversão ("criar conta") #FFA94D #FFB866
brand.highlight-light ouro do logo; pendência que espera o usuário #FFCB53 #FFCB53
brand.on-highlight texto sobre âmbar #1A1A1A #1A1A1A
brand.info ciano da assinatura; dado e informação neutra #00ACF0 #00ACF0

As cinco cores da marca, e quando usar cada uma

O logo é um swoosh de três braços mais a assinatura. Até a spec 0019 a paleta adotava três dos cinco matizes — faltavam o ciano, que é o começo do gradiente da assinatura inteira (#00ACF0 → #005577) e aparecia uma vez cravado no login, e o ouro, primeira parada do braço laranja.

Cor No logo Token Emprego
#FFA94D laranja braço de cima highlight a ação mais desejada da tela
#FFCB53 ouro início do braço laranja highlight.light pendência que espera o usuário
#008B8B teal braço do meio accent marca e navegação — onde o produto se identifica
#00ACF0 ciano início da assinatura info dado e informação neutra
#005577 navy fim da assinatura structure estrutura: cabeçalho, rótulo, moldura

Sem esta tabela, "usar mais cor" vira decisão de gosto a cada tela — foi assim que o laranja chegou a cinco usos no app inteiro enquanto o neutro somava 386.

Nenhuma cor viva serve como texto no tema claro. Sobre painel branco o teal mede 4,15:1, o ciano 2,57:1, o laranja 1,90:1, o ouro 1,51:1. Texto e glifo coloridos usam a tinta correspondente (*-ink), sempre. É o que impede "adotar a marca" de virar "piorar o contraste".

Feedback (semântica)

Token Uso Light Dark (clareado p/ contraste)
feedback.success aprovação, contrato ativo #2E7D32 #5BBE7A
feedback.warning risco moderado, pendência #F59E0B #F7B84D
feedback.error inadimplência, falha, inválido #D32F2F #F0726F
feedback.info dado, aviso neutro (severidade padrão) #00ACF0 #00ACF0

Cada semântica tem cinco peças, e é preciso as cinco (spec 0019): cor viva, tinta de texto (*-ink), cor sobre preenchimento sólido (on-*), fundo de alerta e fundo de chip. Até a 0019 faltavam warning-ink, info-ink, on-warning, on-info e o chip de info — e era nas lacunas que o app improvisava: a taxa de inadimplência do Centro de Operações era pintada com o âmbar vivo, 2,15:1, o número que avisa que algo vai mal sendo o mais difícil de ler da tela.

Tinta (light) Valor Medido no chip
success-ink #1B5E20 6,54:1
warning-ink #AE5700 4,53:1
danger-ink #B71C1C 5,32:1
info-ink #0071B5 4,53:1

As tintas medem contra o chip a 14% — o pior fundo em que aparecem —, não contra o painel: calibrar no painel deixa a tinta confortável solta e apertada dentro do chip. on-warning e on-info são escuros (#0F172A), porque branco sobre âmbar mede 2,15:1 e sobre ciano 2,57:1.

No dark, o feedback usa tons mais claros (menos croma) para manter contraste sobre #0F172A/#1E293B — os valores light seriam escuros demais. No Figma são os primitivos success/400, warning/400, danger/400, ligados só ao modo Dark.

Neutros — Light

Token Valor Medido
bg.default #EDF1F6 —
bg.surface (paper) #FFFFFF 1,13:1 contra o fundo
text.primary #1A1A2E 17,06:1
text.secondary #465264 7,92:1 (AAA)
text.disabled #67768B 4,62:1 — só controle inativo
border.field (borda de campo) #8996AA 3,00:1
border.subtle (filete de painel) #ADB8C8 2,01:1
border.list (linha entre itens) #C9D4E0 1,50:1

O painel é branco puro desde a spec 0019. Antes era #FBFCFE e o fundo #F1F5F9, que diferiam 1,07:1 — quase indistinguíveis. Separar as duas superfícies escurecendo o fundo era a saída óbvia e a errada: derrubaria o contraste de todo texto que assenta nele. Clarear o painel sobe o contraste de tudo que está dentro dele, que é onde vive a maior parte do conteúdo.

text.secondary mira AAA (7:1), não AA. Os 199 usos dele no app estão todos em caption (12px) e body2 (13px): é o corpo do conteúdo, em corpo pequeno, lido oito horas por dia. A tolerância da WCAG a contraste menor pressupõe texto grande — aqui vale o contrário. neutralInk tem o mesmo valor: um conceito, um token.

Separador tem três réguas, e só uma vem da norma. A WCAG 1.4.11 pede 3:1 para "informação necessária para identificar componentes". A palavra que decide é identificar:

Nível Onde Razão Por quê
Campo input, select, textarea 3:1 a borda é o que diz "aqui se digita" — sem ela o campo some. Exemplo canônico da norma.
Painel card, diálogo, cabeçalho de tabela 2:1 o painel é identificado pelo cabeçalho e pelo conteúdo; a borda decora
Lista linha entre itens 1,5:1 separa leitura, não delimita componente

Onde cada nível se aplica (spec 0021): a régua segue a função na tela, não o componente que o DOM usa. O cartão que se repete por registro numa lista é a linha daquela lista — vai em border.list, igual à célula do DataGrid no desktop. Um FormPanel ou um bloco de detalhe é painel, e vai em border.subtle. Ler a estrutura em vez da função foi o que deixou a mesma lista com pesos diferentes nos dois viewports.

A barra do topo não é painel. Filete embaixo, sem raio, nos dois viewports — é divisão entre áreas, não moldura de objeto. O mesmo vale para Popover, Menu, Dialog e Drawer, que já se separam do fundo por sombra.

A spec 0019 aplicou 3:1 aos três e o fix 0004 corrigiu: o contorno virava o elemento mais forte da tela. Pior, a fronteira do painel passava a ser dita três vezes — superfície branca, cabeçalho tingido e borda escura —, e redundância em interface lê-se como ruído.

A régua decorativa pode ser leve porque não trabalha sozinha. Antes da 0019 o divisor a 1,20:1 sumia porque nada mais separava: painel e fundo diferiam 1,07:1 e não havia zebra perceptível. Estrutura vem de superfície e espaço; linha é apoio.

Neutros — Dark (tingidos na marca)

Token Valor Medido
bg.default #0B1220 1,28:1 contra o painel
bg.surface #1E293B —
text.primary #E8EAF0 12,16:1
text.secondary #ACB4C0 7,00:1 (AAA)
text.disabled #8290A4 4,51:1
border.field (borda de campo) #647387 3,03:1
border.subtle (filete de painel) #49586C 2,02:1
border.list (linha entre itens) #364559 1,50:1

O text.secondary do escuro era rgba(232,234,240,0.70) nesta tabela e #9AA4B2 no código — divergência de origem, resolvida na spec 0019 em favor de um valor opaco. Cor de texto por transparência é o que faz o contraste escapar do controle da paleta.

Regra: ao clarear rumo ao branco/preto, reduzir croma (evitar cor saturada nos extremos). Neutros sempre com leve tinta azulada da marca, nunca cinza puro nem #000/#fff cravados.


2. Tipografia

  • Display / títulos: Onest — pesos 400, 500, 600, 700
  • Corpo: Inter — pesos 400, 600, 700
  • Carregadas via Google Fonts (já em apps/web/index.html).

Escala fixa em rem (UI de produto, não fluida), razão ~1.2–1.25:

Token Tamanho Peso Line-height Uso
display 30px Onest 700 1.15 headline de marca (login)
h1 24px Onest 700 1.2 título de página
h2 20px Onest 600 1.25 seção
h3 16px Onest 600 1.3 subtítulo / card
body-lg 15px Inter 400 1.55 texto de destaque
body 14px Inter 400 1.55 corpo padrão
body-sm 13px Inter 400 1.5 apoio
caption 12px Inter 400 1.45 metadados
overline 11px Inter 600 1.4 section labels (caixa-alta, letter-spacing: 0.09em)

Corpo com max-width ~65–75ch. Em texto claro sobre fundo escuro, somar ~0.05–0.1 à line-height.


3. Espaçamento (escala 4pt)

Tokens semânticos, não pixel-nomeados:

Token px
space.2xs 4
space.xs 8
space.sm 12
space.md 16
space.lg 24
space.xl 32
space.2xl 48
space.3xl 64
space.4xl 96

Usar gap para espaçar irmãos; variar o espaçamento para criar hierarquia.


4. Raio

Token px
radius.sm 4
radius.md 6 (default — shape.borderRadius do tema)
radius.lg 12
radius.pill 999

5. Elevação (sutil, tingida)

O app evita sombras genéricas. Duas elevações discretas, com tinta azul-profundo:

Token Valor
elevation.card 0 1px 2px rgba(0,46,66,0.04)
elevation.raised 0 1px 2px rgba(0,46,66,0.04), 0 8px 32px rgba(0,46,66,0.08)

Sem glassmorphism, sem sombras difusas decorativas.


6. Componentes-base (inventário para o Figma)

Primeira passada de componentes (com variantes e estados):

  • Button — primary (azul), secondary/outlined (teal), text/link (teal), cta (âmbar). Estados: default, hover, disabled, loading.
  • Text field (MUI TextField small) — default, focus, error (com helperText), disabled. Label com shrink controlado.
  • Panel / Card (padrão FormPanel) — cabeçalho com overline + corpo. Sem stripe lateral, sem sombra genérica.
  • Alert — error/warning/success/info, com lista de detalhes opcional (padrão do form de imobiliária).
  • Status chip — mapeado às semânticas: aprovado (success), pendente (warning), inadimplente (error), rascunho (neutro).
  • Sidebar item (do custom-sider) — default, ativo (azul), com badge de contagem, colapsado (só ícone).
  • Toast — notificação não-bloqueante, auto-dismiss, canto inferior: sucesso e info. Badge de ícone na cor cheia da severidade + glifo branco + título + mensagem + fechar. Passageiro, sem ação de confirmação.
  • Modal — dois usos, cada um uma variante (Tipo), rodapé reusando o Button:
  • Confirmação (warning) — ação que precisa de confirmação (Cancelar / Continuar).
  • Erro (danger) — falha com bloco "Detalhes técnicos" (código, referência, quando, mensagem) + Copiar para o suporte; casa com o ApiError do front (code, status, message, details[]).

Roteamento de feedback (qual componente para cada caso)

Situação Componente
Sucesso / info (confirmação passageira) Toast — não-bloqueante, auto-dismiss
Ação que exige confirmação Modal Confirmação (warning)
Falha de operação (com detalhe p/ suporte) Modal Erro (danger)
Validação de formulário inline no campo (Text Field error) + scroll ao 1º erro — nunca toast/modal
Aviso persistente no contexto Alert — fica na página, inline

Estruturar no Figma como variáveis (cores light/dark, tipo, espaço, raio) + componentes com variantes, numa página Design System.