Design System

Guia vivo de componentes, padrões e princípios do Glyco.

Visão geral

O Glyco é uma plataforma de gestão clínica para endocrinologistas. Este design system documenta cada decisão visual, de interação e conteúdo para que qualquer pessoa da equipe entregue experiências consistentes, acessíveis e previsíveis.

Consistente

Um token. Um componente. Mesma aparência em toda a aplicação.

Acessível

WCAG AA por padrão. Teclado, leitor de tela e redução de movimento.

Clínico

Confiança acima de decoração. Dado legível antes de efeito visual.

Princípios

Quatro regras que guiam toda decisão de produto.

  1. Clareza antes de engenhosidade. Se precisar explicar, não está claro.
  2. Dado legível. Gráficos, tabelas e KPIs devem ser lidos antes de interpretados.
  3. Feedback imediato. Toda ação tem resposta em até 100ms (visual), mesmo que a operação real leve mais.
  4. Respeito ao contexto. Médicos usam o Glyco entre consultas. Economize cliques, tempo e ambiguidade.

Marca

A identidade visual do Glyco.

Glyco

Regras do wordmark

  • Fonte: IBM Plex Sans Bold (700)
  • Letter-spacing: -0.04em
  • Cor: sempre var(--foreground) — logo e texto andam juntos em ambos os temas
  • Área de proteção: metade da altura do "G" ao redor do conjunto
  • Tamanho mínimo: 20px de altura do wordmark

Cores

Todos os componentes consomem tokens CSS — nunca hex direto. Light e dark são pares projetados juntos.

Superfícies & texto

Aa
background
--bg
Aa
surface
--bg-elev
Aa
muted
--muted
Aa
foreground
--fg

Acento & estado

Aa
accent
#3b82f6
Aa
success
#10b981
Aa
warning
#f59e0b
Aa
danger
#ef4444
Aa
chart-5
#8b5cf6

IA & Automação

Elementos tocados pelo copiloto IA usam uma paleta roxa dedicada — aplicada apenas a textos/labels de IA para criar afinidade visual sem competir com o azul de acento principal.

Aa
ai-fg
#8B5CF6
Aa
ai-fg-soft
#C896FC
Aa
ai-bg
rgba(168,85,247,.14)
Aa
ai-gradient
#c084fc → #f472b6
  • #8B5CF6 — textos primários de IA: pill "Copiloto IA", sidebar "Automações & IA", label "Automações IA", cabeçalho "IA sugere", botão "Revisar"
  • #C896FC — métricas secundárias de IA (delta do KPI, legendas pequenas) onde o contraste precisa ser mais leve
  • ai-gradient — números-destaque e ícones principais de IA, via background-clip: text

Temas escopados

O mockup do dashboard da landing page aceita um toggle independente do tema global: .app.is-light sobrescreve as CSS vars (--bg, --bg-elev, --fg, etc.) apenas dentro do container .app. Isso permite alternar o mockup sem arrastar o resto do site junto.

Escopar overrides por componente

Usar .container.is-light { --bg: #fff; ... } quando um bloco precisa de tema isolado (ex.: showcase, mockup, preview).

Não duplicar hex em cada regra

Sobrescreva só os tokens — os seletores descendentes continuam consumindo var(--bg), var(--fg) normalmente.

Accent por card

Cards de feature usam um token --card-accent por instância — passado via style inline — para diferenciar visualmente cada pilar sem quebrar o sistema. No hover o card sobe levemente e a border-color assume o accent.

Aa
accent-amber
#f59e0b
Aa
accent-pink
#ec4899
Aa
accent-cyan
#06b6d4
Aa
accent-violet
#8B5CF6
  • Amber — alertas clínicos e sinais de atenção (ex.: card de adesão, lembretes)
  • Pink — relacionamento com paciente (WhatsApp, follow-up, retenção)
  • Cyan — dados e bioimpedância (métricas, gráficos secundários)
  • Violet — IA e automação (compartilhado com a paleta de IA)
  • Aplicação: <div class="feature-card" style="--card-accent:#f59e0b"> — o CSS usa border-color: var(--card-accent) no hover e cor no ícone

Uso

Use tokens semânticos

color: var(--danger) para mensagens de erro, nunca o hex direto — o token muda entre temas.

Não use cor como único sinal

Um campo inválido precisa de borda vermelha e texto de erro abaixo. Pense em usuários daltônicos.

Tipografia

Dois tipos, três pesos, escala fixa. Sem exceções.

Famílias

  • Brand: IBM Plex Sans Bold — só para o wordmark "Glyco"
  • UI: Geist — todos os textos de interface
  • Mono: Geist Mono — códigos, IDs, valores tabulares
  • Display (mockups): SF Pro Display — stats e métricas de destaque em mockups de dashboard/showcase; stack "SF Pro Display", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif

Rotação de palavras no hero

Headline rotativo (que pensa em [palavra].) trava a largura do contêiner via JS após document.fonts.ready, medindo a palavra mais longa e aplicando width: Npx. A pontuação final fica dentro do span rotativo para acompanhar o fim da palavra — a largura reservada absorve a variação sem causar reflow da página.

Escala

display · 40/-0.04/700
Gestão clínica clara
h1 · 28/600
Título de página
h2 · 20/600
Seção
h3 · 16/600
Subseção
body · 14/400
Texto padrão da interface — labels, parágrafos, listas.
small · 12/400
Legendas, hints, metadados.
caps · 11/500/0.08em
Labels de seção

Regras

  • Body nunca abaixo de 14px na web — 16px em formulários mobile (evita zoom automático do iOS)
  • Line-height 1.5–1.6 para parágrafos, 1.2–1.3 para títulos
  • Tabular figures (font-variant-numeric: tabular-nums) em preços e tabelas numéricas
  • Truncar só quando o espaço é realmente finito — caso contrário, wrap

Preço (BRL)

Preços de planos e números financeiros seguem formatação brasileira padrão: R$ + espaço + milhares com ponto + vírgula decimal quando aplicável. Peso 600, letter-spacing -0.02em, tabular-nums obrigatório para alinhar centavos em coluna.

preço · 32/600
R$ 1.890
preço · 40/700
R$ 3.999/mês
preço inline · 14/500
R$ 38.420
  • Sufixos (/mês, /ano) em peso 500 e var(--fg-muted) para hierarquia
  • Centavos só quando relevantes — valores de plano arredondam para inteiro
  • Variação percentual em var(--success) ou var(--danger) com seta opcional

Espaçamento

Escala base 4px. Só valores múltiplos de 4 aparecem em interfaces finais.

space-1 · 4px
space-2 · 8px · gap padrão entre elementos correlatos
space-3 · 12px
space-4 · 16px · padding de cards
space-5 · 20px
space-6 · 24px · entre seções dentro de cards
space-8 · 32px · entre blocos
space-12 · 48px · entre seções de página

Hierarquia vertical

  • Dentro de um campo: 6px entre label → input → hint/error
  • Entre campos: 16px
  • Entre grupos de campos: 24–32px
  • Entre seções de página: 40–48px

Bordas & Raio

Raio dá linguagem. Glyco usa cantos suaves, nunca quadrados nem pílulas excessivas.

4px · badges, checkbox
8px · inputs, botões
10px · cards pequenos
12px · modais, cards grandes

Bordas

  • Padrão: 1px sólida var(--border)
  • Sutil (divisores internos): 1px var(--border-subtle)
  • Foco: 2px var(--ring) com outline-offset de 2px — jamais remover focus-visible

Elevação

Três níveis — mais que isso polui. Sombras subirem apenas para sinalizar hierarquia espacial.

shadow-sm
0 1px 2px rgba(0,0,0,.08)
shadow-md
0 4px 10px rgba(0,0,0,.12)
shadow-lg
0 8px 24px rgba(0,0,0,.18)

sm: thumbs, chips levitando. md: menus, popovers. lg: modais, sheets.

Ícones

Uma família por camada. Nunca emojis como ícones estruturais.

Regras

  • Família padrão: lucide-react — stroke de 1.75, 24px
  • Navegação: iconsax-react — usado na sidebar
  • Tamanhos: 14 · 16 · 18 · 20 · 24 — nunca intermediários
  • Cor: currentColor + cor do texto do container
  • Nunca: emojis (🎨 🚀) como ícones de UI estrutural

Botões

Uma CTA primária por tela. Demais ações ficam secundárias ou ghost.

Variantes

Tamanhos

Com ícone

Loading

CTA primário (landing)

O botão "Começar agora" da landing usa uma variante especial: fundo #3DB981 (brand-green), halo branco sutil via box-shadow e pulse de borda no hover. Reservado para a CTA principal do hero e final de seção — nunca dentro do produto.

  • Radius 999px (pill) — único botão do sistema que usa pill
  • Hover: translateY(-1px) + aumento do halo branco interno + sombra externa mais difusa
  • Uma única ocorrência por seção (hero, pricing, CTA final) — nunca repetido em listas
  • Texto em #fff, peso 600 — garantir contraste ≥ 4.5:1 contra #3DB981

Regras

  • Altura mínima de 36px; 44px em mobile
  • Feedback de press em 80ms (transform: scale(.98))
  • Em ações destrutivas (excluir, cancelar assinatura), sempre confirmar em modal
  • Botões carregando ficam desabilitados com spinner visível

Inputs

Label sempre visível. Placeholder é exemplo, não substituto de label.

Estados

Nome completo conforme documento
E-mail inválido — falta o domínio após @.
0 / 500 caracteres

Regras

Valide no blur

Mostre erro quando o usuário sai do campo — não a cada tecla digitada.

Não use placeholder como label

Ele some ao digitar e prejudica usuários de leitor de tela.

Input type semântico

Use type="email", tel, number — ativa o teclado correto no mobile.

Não desative o zoom

Fontes menores que 16px no mobile causam zoom automático do iOS. Use 16px no body de inputs.

Selects & Controles

Uma escolha entre poucas opções? Radio. Entre muitas? Select. Múltiplas? Checkbox.

Toggle

Para ligar/desligar uma configuração que aplica imediatamente. Não use quando é preciso confirmar.

Acessibilidade

  • role="switch" + aria-checked
  • Controlável por Space e Enter
  • Área clicável ≥ 44×32 · thumb de 28px
  • Transição do thumb em 180ms · cubic-bezier(0.4, 0, 0.2, 1)

Tabs

Para alternar entre visões irmãs. Nunca use para navegação entre páginas.

  • Setas ← → navegam entre tabs quando foco está na tab
  • Tab ativa com aria-selected="true"
  • Conteúdo abaixo muda sem transição — evita desorientação

Badges

Metadados curtos ao lado de títulos ou em tabelas. Nunca clicáveis.

Default Agendada Confirmada Aguardando Cancelada

Com ponto

Ativo Inativo

Avatares

Mostra a pessoa. Sem foto, mostra iniciais — nunca um ícone genérico.

PL PL PL CF

Avatar group

PL CF AR +5

Cards

Agrupamento de conteúdo relacionado. Padding 20px, radius 10px, border sutil.

Receita do mês
R$ 38.420
+12,4% vs mês anterior
Consultas
142
Agendadas neste mês
Pacientes ativos
284
Nos últimos 90 dias

Subgrid cross-card (plan cards)

Cards de planos (pricing) precisam alinhar linhas equivalentes entre si — header, preço, lista de features e CTA ficam todos na mesma altura mesmo quando o conteúdo varia. Conseguimos isso com display: grid no container + grid-template-rows: auto auto 1fr auto e display: subgrid nos cards filhos.

.plans-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto auto 1fr auto; /* header / preço / features / cta */
  gap: 24px;
}
.plan-card {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 4;
}
  • Título curto de um plano não "levanta" o preço dos outros — as linhas do grid pai dominam
  • Fallback para navegadores sem subgrid: @supports not (grid-template-rows: subgrid) aplica min-height por slot
  • Regra: só usar quando existe relação semântica linha-a-linha entre cards — não aplicar em cards avulsos

Tabelas

Para dados densos. Sempre com linha zebrada sutil, cabeçalho em caps, sort visível.

Paciente Condição Aderência Próx. retorno
CFCarlos Ferreira Diabetes T2 42% 17 mar 26
ARAna Rodrigues Hipotireoid. 88% 28 mai 26
RNRoberto Nascimento Diabetes T2 28% 10 abr 26
  • Colunas numéricas com font-variant-numeric: tabular-nums — evita "dança" na ordenação
  • Hover na linha destaca toda a linha, não célula por célula
  • Clicar na linha abre detalhe; botões de ação ficam no final

Toasts & Alerts

Feedback temporário (toast) ou persistente (alert). A diferença importa.

Toasts — some em 4–5s

Consulta salva

Carlos Ferreira — 17 de março, 14:30.

Falha ao enviar

Sem conexão. Os dados ficam salvos localmente até reconectar.

Alerts — persistentes

3 pacientes precisam de retorno em atraso. Clique para revisar.
Sua licença CRM vence em 12 dias. Renovar agora →

Regras

  • Toasts aparecem no canto inferior direito — aria-live="polite"
  • Erros críticos usam alert persistente, não toast
  • Nunca empilhar mais de 3 toasts — colapse ou resuma

Loading

Skeleton para conteúdo estruturado. Spinner para ações pontuais. Nunca deixe tela em branco.

Skeleton

Spinner

Carregando pacientes…

Regras

  • Skeleton respeita o layout real — evita Cumulative Layout Shift
  • Operações > 300ms devem mostrar feedback
  • Respeita prefers-reduced-motion — shimmer vira fundo estático

Empty States

Primeiro contato importa. Explique, convide e direcione.

Nenhum paciente cadastrado

Comece adicionando seu primeiro paciente. Leva menos de um minuto.

Formulários

Quanto menos campos, melhor. Quebre fluxos longos em passos.

Padrões

  • Label acima, sempre. Placeholder só complementa.
  • Campos obrigatórios marcados com asterisco *. Opcionais raramente precisam de marcação.
  • Validação no blur, não no keystroke. Exceção: senha com requisitos progressivos.
  • Mensagem de erro em vermelho, abaixo do campo, com ícone e causa + solução ("E-mail inválido — falta @dominio.com").
  • Foco automático no primeiro campo inválido após submit.
  • Auto-save em formulários longos — nunca perder dados por acidente.
  • Botão primário à direita, secundário à esquerda (convenção de leitura esquerda → direita = confirmação no final).

Ordem dos campos

  1. Identificação (nome, CPF)
  2. Contato (e-mail, telefone)
  3. Dados clínicos
  4. Preferências / opcionais
  5. Observações / notas livres

Gráficos

Cada tipo de dado tem um gráfico certo. Cor nunca é o único sinal.

Bar — comparar

Paleta padrão

chart-1
#3b82f6
chart-2
#10b981
chart-3
#f59e0b
chart-4
#ef4444
chart-5
#8b5cf6

Tipos por dado

DadoGráficoQuando
Tendência no tempoLinha / SparklineReceita, peso, glicemia ao longo de meses
ComparaçãoBarraReceita por tipo de consulta
ProporçãoDonut (≤5 partes)% de aderência por condição
DistribuiçãoHistograma / BoxIdade dos pacientes
Evolução cumulativaÁrea empilhadaDRE mensal

Regras

  • Legenda visível perto do gráfico — nunca forçar o olho a viajar
  • Tooltip no hover mostra valor exato em tabular-nums
  • Eixo Y começa em 0 por padrão — truncar só com motivo e aviso visual
  • Gráficos interativos têm alternativa em tabela (leitores de tela)
  • Padrões/texturas em séries para daltônicos em gráficos críticos

Movimento

Animação comunica causa e efeito. Nunca decora.

Durações

UsoDuraçãoExemplo
Micro-interação80–150msPress em botão, hover de card
Transição de estado180–250msToggle, tab, accordion
Mudança de tela250–350msModal, sheet, navegação
Entrada complexa350–500msOnboarding, celebrações

Easings

ease-out
ease-in-out
spring
linear (evitar)

Count-up ao scroll

Métricas da landing (92% respondem em <1h, +R$ 38.420) animam de 0 até o valor final assim que entram no viewport — dispara uma única vez por IntersectionObserver com threshold: 0.4. Duração 900–1200ms, easing cubic-bezier(.22,1,.36,1) (ease-out forte) para desacelerar nos últimos 30% do valor.

const io = new IntersectionObserver((entries) => {
  for (const e of entries) {
    if (!e.isIntersecting) continue;
    const el = e.target;
    const to = Number(el.dataset.to);
    const start = performance.now();
    const dur = 1000;
    const ease = (t) => 1 - Math.pow(1 - t, 3);
    const tick = (now) => {
      const t = Math.min(1, (now - start) / dur);
      el.textContent = Math.round(to * ease(t)).toLocaleString('pt-BR');
      if (t < 1) requestAnimationFrame(tick);
    };
    requestAnimationFrame(tick);
    io.unobserve(el);
  }
}, { threshold: 0.4 });
document.querySelectorAll('[data-countup]').forEach((el) => io.observe(el));
  • Valores finais com toLocaleString('pt-BR') — "1.000" em vez de "1,000"
  • prefers-reduced-motion: reduce pula o count e pinta o valor final direto
  • Reservar largura com tabular-nums — evita reflow enquanto o número cresce
  • Disparo único: io.unobserve(el) após o primeiro hit

Rotação de palavras (hero)

Headline rotativo alterna entre 3–4 palavras a cada ~2.4s. A largura do span é travada ao maior item via JS após document.fonts.ready. Transição: opacity 250ms ease-in-out, troca de texto no meio (0% opacity), sem slide vertical — evita a sensação de "jump".

  • Pontuação final dentro do span rotativo (endocrinologia.) — acompanha a palavra
  • Resize debounced em 150ms recalcula a largura
  • Pausa quando a aba perde foco (visibilitychange) para economizar CPU

Regras

  • Entrada: ease-out (começa rápido, desacelera)
  • Saída: ease-in (acelera e sai) — 60–70% da duração da entrada
  • Animar apenas transform e opacity — nunca width/height/top/left
  • Respeitar prefers-reduced-motion: reduce — animações viram trocas instantâneas
  • Toda animação é interrompível — clique do usuário cancela

Acessibilidade

Não é feature. É qualidade básica. WCAG AA é o piso, não o teto.

Princípios

  1. Perceptível — cor não é o único sinal, alt text em imagens, contraste 4.5:1 em texto normal
  2. Operável — tudo acessível por teclado, sem trap de foco, targets de 44×44px
  3. Compreensível — labels claras, erros descritivos, navegação previsível
  4. Robusto — semântica HTML primeiro, ARIA só quando necessário

Checklist de entrega

  • ☑ Tab flui em ordem visual lógica
  • ☑ Focus ring sempre visível (:focus-visible)
  • ☑ Botões só com ícone têm aria-label
  • ☑ Formulários têm <label for> explícito
  • ☑ Erros usam aria-live="polite" ou role="alert"
  • ☑ Leitor de tela testado (VoiceOver no macOS/iOS)
  • ☑ Zoom de 200% não quebra layout
  • prefers-reduced-motion respeitado

Contraste

Texto legível não é opcional. Mínimo WCAG AA.

Texto sobre superfícies (verificado)

Texto normal · 14px
fg sobre bg · ~14:1 ✓ AAA
Muted · 14px
fg-muted sobre surface · ~4.6:1 ✓ AA
Botão primário · 14px
branco sobre accent · ~4.5:1 ✓ AA
Destrutivo · 14px
branco sobre danger · ~4.6:1 ✓ AA

Mínimos

  • Texto normal (<18px): 4.5:1
  • Texto grande (≥18px ou 14px bold): 3:1
  • Ícones e elementos gráficos: 3:1
  • Focus ring vs fundo: 3:1

Voz & Tom

Como o Glyco fala com o usuário.

Atributos

  • Direto. Vai ao ponto. Sem enrolação.
  • Profissional, mas humano. Nem robótico, nem informal demais.
  • Acolhedor em erros. Nunca culpa o usuário. "Não foi possível" > "Você errou".
  • Específico. "Consulta salva" > "Sucesso".

Exemplos

Sim

"Consulta de Carlos Ferreira salva para 17/03 às 14:30."

Não

"Operação realizada com sucesso!"

Sim

"E-mail inválido — falta o domínio após @."

Não

"ERRO: Entrada inválida."

Sim

"Adicione seu primeiro paciente para começar."

Não

"Nenhum registro encontrado no banco de dados."

Do's & Don'ts

Resumo rápido para revisões.

Uma CTA primária por tela

Facilita a decisão. Ações secundárias ficam ghost ou secondary.

Múltiplos botões azuis

Dilui a hierarquia — o usuário não sabe qual é a ação principal.

Espaço em branco

Respira. Agrupa. Dá hierarquia sem linhas.

Separadores em tudo

Ruído visual. Use whitespace antes de linha.

Escreva "Excluir paciente"

Ação + objeto específico. Usuário sabe o que vai acontecer.

Escreva "Confirmar"

Confirmar o quê? Labels genéricas quebram a confiança.

Icone + texto

Na navegação, sempre os dois — ícones sozinhos são ambíguos.

Só ícone sem aria-label

Leitor de tela fica perdido. Sempre label, mesmo que visualmente escondida.

Este documento é parte do Glyco. Mudanças seguem o fluxo: proposta → revisão → implementação em src/components/ → atualização aqui.