Design System
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.
Um token. Um componente. Mesma aparência em toda a aplicação.
WCAG AA por padrão. Teclado, leitor de tela e redução de movimento.
Confiança acima de decoração. Dado legível antes de efeito visual.
Princípios
Quatro regras que guiam toda decisão de produto.
- Clareza antes de engenhosidade. Se precisar explicar, não está claro.
- Dado legível. Gráficos, tabelas e KPIs devem ser lidos antes de interpretados.
- Feedback imediato. Toda ação tem resposta em até 100ms (visual), mesmo que a operação real leve mais.
- Respeito ao contexto. Médicos usam o Glyco entre consultas. Economize cliques, tempo e ambiguidade.
Marca
A identidade visual do 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
Acento & estado
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.
#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 leveai-gradient— números-destaque e ícones principais de IA, viabackground-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.
- 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 usaborder-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
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.
- Sufixos (
/mês,/ano) em peso 500 evar(--fg-muted)para hierarquia - Centavos só quando relevantes — valores de plano arredondam para inteiro
- Variação percentual em
var(--success)ouvar(--danger)com seta opcional
Espaçamento
Escala base 4px. Só valores múltiplos de 4 aparecem em interfaces finais.
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.
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.
0 1px 2px rgba(0,0,0,.08)
0 4px 10px rgba(0,0,0,.12)
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
Inputs
Label sempre visível. Placeholder é exemplo, não substituto de label.
Estados
@.
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.
Com ponto
Avatares
Mostra a pessoa. Sem foto, mostra iniciais — nunca um ícone genérico.
Avatar group
Cards
Agrupamento de conteúdo relacionado. Padding 20px, radius 10px, border sutil.
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.
- 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)aplicamin-heightpor 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.
- 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
Modais & Dialogs
Bloqueiam o fluxo principal. Use só quando a ação exige decisão imediata.
Excluir consulta?
Esta ação não pode ser desfeita. A consulta de Carlos Ferreira em 17 de março será removida da agenda.
Regras
- ESC fecha modais não-destrutivos
- Foco vai para o primeiro elemento interativo na abertura e retorna ao trigger no fechamento
- Backdrop
rgba(0,0,0,.5)— mais forte ainda pode obscurecer conteúdo crítico atrás - Máximo de 1 modal por vez — nunca modal dentro de modal
Toasts & Alerts
Feedback temporário (toast) ou persistente (alert). A diferença importa.
Toasts — some em 4–5s
Carlos Ferreira — 17 de março, 14:30.
Sem conexão. Os dados ficam salvos localmente até reconectar.
Alerts — persistentes
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
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.
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
- Identificação (nome, CPF)
- Contato (e-mail, telefone)
- Dados clínicos
- Preferências / opcionais
- 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
Tipos por dado
| Dado | Gráfico | Quando |
|---|---|---|
| Tendência no tempo | Linha / Sparkline | Receita, peso, glicemia ao longo de meses |
| Comparação | Barra | Receita por tipo de consulta |
| Proporção | Donut (≤5 partes) | % de aderência por condição |
| Distribuição | Histograma / Box | Idade dos pacientes |
| Evolução cumulativa | Área empilhada | DRE 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
| Uso | Duração | Exemplo |
|---|---|---|
| Micro-interação | 80–150ms | Press em botão, hover de card |
| Transição de estado | 180–250ms | Toggle, tab, accordion |
| Mudança de tela | 250–350ms | Modal, sheet, navegação |
| Entrada complexa | 350–500ms | Onboarding, celebrações |
Easings
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.
- Valores finais com
toLocaleString('pt-BR')— "1.000" em vez de "1,000" prefers-reduced-motion: reducepula 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
transformeopacity— nuncawidth/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
- Perceptível — cor não é o único sinal, alt text em imagens, contraste 4.5:1 em texto normal
- Operável — tudo acessível por teclado, sem trap de foco, targets de 44×44px
- Compreensível — labels claras, erros descritivos, navegação previsível
- 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"ourole="alert" - ☑ Leitor de tela testado (VoiceOver no macOS/iOS)
- ☑ Zoom de 200% não quebra layout
- ☑
prefers-reduced-motionrespeitado
Contraste
Texto legível não é opcional. Mínimo WCAG AA.
Texto sobre superfícies (verificado)
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.
src/components/ → atualização aqui.