Pular para o conteúdo principal

Sistema de Cores do Aplica DS

Premissa

Cores em um Design System são um subsistema completo — não estética, mas estrutura. Cada decisão de cor tem uma razão semântica: ela existe porque representa um papel na interface, não porque parece bonita.

O Aplica DS parte de um princípio simples: nenhuma cor hardcoded. Toda cor é um token com propósito. O mesmo token semântico pode assumir valores diferentes dependendo de brand, modo e superfície — sem que o componente que o consome precise saber disso.


Taxonomia de Cores

O sistema organiza cores em grupos com responsabilidades distintas. Cada grupo responde a uma pergunta diferente.

Brand Colors

"Quem é a marca?"

Cores que carregam a identidade visual. Cada brand define até três cores primárias:

TokenSemântica
brand.firstCor principal da marca
brand.secondCor secundária
brand.thirdCor terciária

Essas cores formam a base de todos os gradientes da marca e influenciam a geração das cores neutras (neutrals).

Interface — Function

"Qual é a hierarquia de ação?"

Cores que sinalizam ações interativas em componentes:

TokenSemântica
interface.function.primaryAção principal — botão de destaque, CTA
interface.function.secondaryAção secundária — pode coexistir com vários elementos do mesmo nível
interface.function.linkÂncoras, botões que parecem links

A distinção entre Brand e Function é fundamental: Brand comunica identidade; Function comunica interatividade. Um elemento com cor de Brand não é necessariamente clicável. Um elemento com cor de Function sempre é.

Interface — Feedback

"O sistema está respondendo a quê?"

TokenSemântica
interface.feedback.infoNotificações neutras, informações sem viés positivo/negativo
interface.feedback.successAção concluída, reforço positivo
interface.feedback.warningAtenção necessária, sem bloqueio de uso
interface.feedback.dangerErro, bloqueio, ação destrutiva

Cada feedback tem variante default (mais suave) e secondary (mais saturada). Isso permite usar a variante suave para backgrounds e a saturada para bordas ou ícones no mesmo componente.

Nota sobre "Danger" vs "Erro": O nome danger é intencional. Além de erros, essa cor é usada em ações potencialmente destrutivas (ex: "Excluir conta") — onde não há erro, mas há risco real.

Product

"Qual é o contexto de produto?"

Cores para necessidades de negócio que não se encaixam no vocabulário universal de interface. Alguns produtos precisam de linguagens visuais completamente próprias — indicadores de temperatura de alimentos, selos de promoção, sinalização de cashback, tiers de assinatura premium, indicadores de risco, badges de seller — e é para isso que a categoria Product existe.

[!IMPORTANT] Product nasce no config, antes de qualquer camada. Entender essa origem é essencial para que designers mantenham intenção desde o início do trabalho em um sistema visual. Toda cor de produto começa como uma decisão deliberada de matiz hex, registrada explicitamente.

Origem: O Config é a fonte de verdade

Tudo começa no arquivo de configuração do tema (*.config.mjs). As cores de produto são declaradas em dois passos:

Passo 1 — Declaração de matizes (colors):

colors: {
// ... (brand, action, feedback)

// Product: cada item tem default + variante alternativa (_alt)
product_promo: '#6BC200', // matiz principal de promo
product_promo_alt: '#D2FD9D', // matiz alternativa/secundária
product_cashback: '#FFBB00',
product_cashback_alt: '#FFF94F',
product_premium: '#B200AF',
product_premium_alt: '#EBC2DD'
}

Passo 2 — Mapeamento semântico (mapping.product):

mapping: {
product: {
promo_default: 'product_promo', // → variante padrão
promo_secondary: 'product_promo_alt', // → variante alternativa
cashback_default: 'product_cashback',
cashback_secondary:'product_cashback_alt',
premium_default: 'product_premium',
premium_secondary: 'product_premium_alt'
}
}

Esse mapeamento conecta o nome livre (product_promo) ao papel semântico (promo_default). A partir daqui, o engine faz todo o trabalho automaticamente.

Pipeline: da matiz hex ao token consumível

Cada cor de produto percorre exatamente o mesmo pipeline que Brand e Interface:

┌─────────────────────────────────────────────────────────────────────┐
│ CONFIG (*.config.mjs) │
│ colors.product_promo = '#6BC200' │
│ mapping.product.promo_default = 'product_promo' │
│ │
│ ▼ color-decomposer.mjs │
│ Decomposição OKLCh → 19 níveis × (surface, txtOn, border) │
│ + 15 níveis de neutrals × (surface, txtOn, border) │
│ + 6 aliases de behavior │
│ │
│ ▼ Brand Layer (_brand.json) │
│ brand.color.product.positive.promo_default.{lowest→highest} │
│ brand.color.product.negative.promo_default.{lowest→highest} │
│ │
│ ▼ Mode Layer (light.json / dark.json) │
│ mode.color.product.promo_default.{lowest→highest} │
│ (light = positive, dark = inverted + chroma × 0.85) │
│ │
│ ▼ Surface Layer (positive.json / negative.json) │
│ surface.color.product.promo.default.{lowest→highest}.{bg/txtOn/b/txt} │
│ │
│ ▼ Semantic Layer (default.json) │
│ semantic.color.product.promo.default.{lowest→highest}.{bg/txtOn/b/txt}│
└─────────────────────────────────────────────────────────────────────┘

Cada item produz 5 intensidades (lowest, low, default, high, highest) × 4 propriedades (background, txtOn, border, txt) = 20 tokens por variante (desde 3.6.0). Com default + secondary, são 40 tokens por item de Product.

[!CAUTION] O custo de cada cor é exponencial. Menos é mais.

Cada item de Product não é "só uma cor" — é uma cascade exponencial:

AçãoTokens gerados
1 item (2 variantes)40 tokens na camada Semantic (desde 3.6.0)
× 4 camadas (Brand, Mode, Surface, Semantic)~160 tokens por tema
× N temas (ex: 4 marcas)~640 tokens no sistema total

Os 3 itens padrão (promo, cashback, premium) já representam ~480 tokens por tema. Cada item adicional acelera o crescimento.

Mais tokens = mais caos: perda de performance (arquivos CSS/nativos maiores, builds mais lentos), aumento de complexidade (mais decisões para designers, mais propriedades para engenheiros), e entropia (tokens subutilizados geram confusão e dívida técnica).

Antes de adicionar um novo item Product, pergunte:

  1. Essa necessidade pode ser resolvida com Feedback (info/success/warning/danger)?
  2. Pode ser resolvida com uma variante de Brand existente?
  3. Mais de 2 componentes realmente precisam dessa cor?

Se alguma resposta for sim, não crie um item Product.

Itens padrão fornecidos pelo Theme Engine

ItemVariante DefaultVariante SecondaryUso típico
promoproduct_promoproduct_promo_altPromoções, descontos, badges de oferta
cashbackproduct_cashbackproduct_cashback_altRetorno financeiro, recompensas
premiumproduct_premiumproduct_premium_altTiers, assinaturas, status especiais

[!WARNING] Os itens são extensíveis, mas com custo. Para adicionar um novo item, basta declará-lo em colors e mapping.product no config do tema. O pipeline criará automaticamente toda a paleta. Porém, leia o alerta acima — cada item novo propaga dezenas de tokens por todas as camadas e todos os temas. Documente a justificativa e valide que a necessidade não pode ser resolvida com tokens existentes.

Textos de Produto

Além do trio funcional (background/txtOn/border), o sistema gera tokens de texto dedicados para contextos de conteúdo puro — onde o texto é colorido mas não há superfície de fundo associada:

semantic.color.text.promo → texto promocional solto
semantic.color.text.promo_secondary → variante secundária
semantic.color.text.cashback → texto de cashback
semantic.color.text.premium → texto de tier premium

Diferença entre Product e Feedback

Interface — FeedbackProduct
EscopoUniversal (toda UI precisa de info/success/warning/danger)Específico do negócio (nem toda UI precisa de promo)
SchemaRígido — fixo no engine, não extensívelAberto — novos itens via config
IntençãoComunicar resultado de ações do sistemaComunicar contexto de domínio do produto
Exemplo"Senha salva com sucesso" (success)"20% de cashback nesta compra" (cashback)

Ambient — Grayscale

"Preciso de profundidade sem interferência de cor?"

Escala fixa de 15 níveis (5–140) com valores neutros puros (ausência de matiz).

A decisão de limitar o uso de Grayscale estritamente a elementos estruturais (acabamentos, bordas, linhas) e sombras (depth) não é arbitrária. Ela é fundamentada fortemente em acessibilidade (A11y) e boas práticas de mercado:

  1. Bordas e Divisores (WCAG 1.4.11 - Non-text Contrast & WCAG 1.4.1 - Use of Color):

    • A espinha dorsal arquitetural de uma UI (seus limites, inputs e separadores de dados) precisa ser infalível. Cinzas puros garantem que a luminância (variável crítica para contraste) seja perfeitamente previsível em qualquer monitor, facilitando o cumprimento dos 3:1 exigidos.
    • Pela regra 1.4.1, a cor não deve ser o único meio visual de informação. Ao construir o "esqueleto" do produto em Grayscale, garantimos que ele funciona por puro contraste. Isso reserva e isola o uso de cores vivas e "Neutrals" coloridos apenas para ressaltar hierarquia, áreas de superfície e estados semânticos (ativo, foco, erro). Se tudo for colorido, o cérebro tem Cognitive Overload.
  2. Sombras e Profundidade (Box-shadows):

    • Embora "sombras coloridas" realcem designs soft/glassmorphism na web, em sistemas de alta densidade como o Aplica DS elas introduzem incerteza matemática no cálculo de contraste. Ao sobrepor um shadow translúcido colorido sobre outros fundos da interface, a mistura afeta não apenas a luminosidade, mas também a matriz da cor resultante (hue shift/degradação acromática), impossibilitando a garantia algorítmica de contraste previsível (WCAG).
    • O uso exclusivo em Grayscale (como o #1a1a1a com opacidade controlada) atua puramente como uma subtração de luminância dos pixels inferiores. Isso simula o sombreamento físico real (oclusão) de forma fidedigna e livre de ruídos, criando camadas (elevation) limpas enquanto mantém o contraste calculável e seguro para a interface.

(Nota: Grayscale não é gerada algoritmicamente — são valores fixos por tema, podendo ser sobrescritos via configuração).

NívelValor (padrão)Uso típico
5#f7f7f7Quase branco — background de card
50#aaaaaaTom médio — bordas neutras
100#555555Tom escuro-médio — textos secundários
140#1a1a1aQuase preto — texto principal em dark

Ambient — Neutrals

"Preciso de profundidade com temperatura de marca?"

Escala de 15 níveis gerada a partir da cor base da marca, mas com o croma (intensidade de cor) reduzido significativamente a 10% da original. O resultado são tons quase neutros com uma leve temperatura cromática — criando coerência entre o ambiente e a identidade da marca sem chamar atenção para si mesmos.

Neutrals são usados prioritariamente para backgrounds de seções, superfícies de cards e estados da UI, envelopando o produto.

[!NOTE] Por que Neutrals não são simplesmente "Cinzas"? (A grande confusão) É muito comum times de design perguntarem: "Por que não posso usar um cinza da paleta Grayscale para esse fundo? Por que o cinza não é o 'Neutro' do sistema?"

A resposta está na Harmonia Óptica.

  • Grayscale (Cinza Puro): É a ausência matemática de cor. Quando você coloca um botão com a cor vibrante da marca (ex: Azul) sobre um fundo cinza puro, as cores não conversam. O cinza puro tende a parecer "morto", "sujo" ou arrastar a paleta para um aspecto desbotado quando pareado com cores vivas.
  • Neutrals (Tom Temperado): São o que chamamos de "Cinzas Coloridos". No Aplica DS, os Neutrals injetam 10% da Matiz da sua marca. Se a marca for Azul, o Neutral será um cinza levissimamente azulado. Isso faz com que fundos, bordas e superfícies conversem naturalmente com a marca do produto, criando uma interface harmoniosa, premium e viva, mesmo nas áreas "vazias" da tela.

A regra de ouro: Use Neutrals para desenhar a interface e as superfícies. Use Grayscale apenas quando a cor for proibida (ex: sombras reais, divisor de conteúdo rigoroso, ou text-supports puros).


Decomposição de Cores

Dado uma cor base em hex, o Theme Engine a decompõe automaticamente em três escalas:

Palette — 19 Níveis (10–190)

A escala primária. Cada nível tem três valores calculados:

  • surface — a cor de preenchimento naquele nível
  • txtOn — a cor de texto acessível sobre aquele surface
  • border — a cor de borda naquele nível

Interpolação de luminosidade (OKLCh):

Nível 10 → L ≈ 0.98 (quase branco)
Nível 100 → L = baseL (cor original exata)
Nível 190 → L ≈ 0.05 (quase preto)

Acima do 100: interpolação linear de baseL até 0.98
Abaixo do 100: interpolação linear de baseL até 0.05

O nível 100 é sempre a cor declarada no config — sem round-trip OKLCh, preservando exatamente o valor configurado.

Border: derivada do surface pelo offset configurável (padrão: +10 níveis). Em light mode, a borda é uma sombra mais escura; em dark mode, mais clara.

Neutrals — 15 Níveis (5–140)

Escala desaturada. Mesma estrutura (surface/txtOn/border), mas com croma multiplicado por 0.1 — mantendo apenas 10% da saturação original.

Nível 5 → L ≈ 0.98 (quase branco com temperatura)
Nível 140 → L ≈ 0.05 (quase preto com temperatura)

Neutrals suportam uma cor base alternativa via override.baseColor — permitindo que a temperatura de neutros seja independente da cor principal.

Behavior — 6 Níveis Semânticos

Aliases sobre a palette, com nomes que correspondem a estados de interação:

NomeReferência na PaletteUso
lightestpalette.10Estado mais suave — hover em áreas sensíveis
activepalette.50Foco por teclado, highlight
normalpalette.100Estado padrão de repouso
actionpalette.120Hover do mouse
pressedpalette.140Click/tap, estado ativo
darkestpalette.170Máxima ênfase — raramente usado

Behavior não gera novos cálculos — é um mapeamento semântico sobre a palette existente.


Estratégias de txtOn

O txtOn é o token de texto que garante legibilidade sobre qualquer surface. O sistema suporta três estratégias configuráveis por tema:

1. high-contrast (padrão conservador)

Sempre preto (#000000) ou branco (#ffffff) — o que tiver maior contraste com o surface. Resultado: contraste máximo, sem nuança de cor.

Quando usar: Sistemas onde acessibilidade é prioridade absoluta; interfaces de alta criticidade.

2. brand-tint

Busca na própria palette o nível mais próximo que passa WCAG AA (4.5:1). Para surfaces claras, busca tons escuros da palette; para surfaces escuras, tons claros.

Resultado: Texto que mantém o tom cromático da marca — mais coerente visualmente, acessível.

Quando usar: Quando o identidade visual é importante e a marca tem cores suficientemente saturadas.

3. custom-tint

Aceita uma cor fixa para light e outra para dark. Se a cor configurada não passar WCAG, cai para high-contrast ou brand-tint como fallback declarado.

Quando usar: Quando o brand guideline especifica exatamente a cor de texto a usar, como em superfícies de marca específicas.

Configuração de nível de acessibilidade: O sistema suporta AA (4.5:1 — padrão) ou AAA (7:1). colorContrastDecompose (startDark/startLight) controla qual extremo o algoritmo tenta primeiro.


Dark Mode

Em dark mode, o sistema não redefine as cores — ele inverte a palette:

surface_dark[level] = surface_light[200 - level]

Ou seja:
dark[10] = light[190] → dark mode tem o tom mais escuro no nível mais baixo
dark[100] = light[100] → a cor base permanece (nível 100 não muda)
dark[190] = light[10] → dark mode tem o tom mais claro no nível mais alto

Redução de croma: Dark mode aplica um multiplicador de saturação (darkModeChroma, padrão: 0.85 = 15% menos saturado). Cores muito vibrantes em light mode ficam levemente mais suaves em dark — reduzindo fadiga visual sem perder identidade.

Borders em dark mode: Bordas são geradas em direção oposta (mais claras que o surface, não mais escuras) para manter visibilidade sobre fundos escuros.


Grayscale em Produção

A grayscale tem um arquivo separado (_grayscale.json) porque:

  1. Seus valores são fixos (não calculados algoritmicamente)
  2. Cada tema pode sobrescrever os valores via overrides.grayscale
  3. A geração em separado permite customização sem afetar o pipeline de cores da marca

Temas com temperatura quente podem ter uma grayscale levemente quente (#faf8f5 em vez de #f7f7f7). Temas minimalistas podem usar escala mais fria.


Tokens Gerados por Cor

Para cada alias de cor no config (ex: joy_pink, action_magenta), o engine gera:

brand.color.palette.<level>.surface → 19 valores
brand.color.palette.<level>.txtOn → 19 valores
brand.color.palette.<level>.border → 19 valores

brand.color.neutrals.<level>.surface → 15 valores
brand.color.neutrals.<level>.txtOn → 15 valores
brand.color.neutrals.<level>.border → 15 valores

brand.color.behavior.<state>.surface → 6 valores
brand.color.behavior.<state>.txtOn → 6 valores
brand.color.behavior.<state>.border → 6 valores

Um tema típico com ~20 cores de base gera aproximadamente 1.200 tokens de cor primitivos antes do mapeamento semântico.


Mínimo de Cores para um Tema

GrupoAliasesQtd
brandfirst, second, third3
interface.functionprimary, secondary, link3
interface.feedbackinfo, success, warning, danger (default + secondary cada)8
productpromo, cashback, premium (default + secondary cada)6
Total (template padrão)20

Grayscale é separada e não entra nessa contagem. O grupo product é extensível — temas podem declarar mais ou menos itens conforme a necessidade; os 3 itens acima são o template de referência, não um mínimo rígido.


Acessibilidade como Algoritmo

Acessibilidade não é um checklist — é parte do processo de geração. Para cada par surface/txtOn gerado:

  1. O sistema calcula o contrast ratio via luminância relativa (WCAG)
  2. Se o par falha no nível configurado (AA ou AAA), aciona o mecanismo de fallback
  3. Falhas podem ser aceitas interativamente (modo CLI) ou em modo estrito (falha o build)

O script verify-aa-compliance.mjs pode ser executado independentemente para auditoria de qualquer combinação de tema.


Evolução do Sistema de Cores

FaseAbordagemLimitação
AlphaPaleta manual, darken/lighten HSLSem consistência perceptual; tons distorcidos em saturações altas
V1Algoritmo documentado, escala formalizadaAinda HSL; racional claro mas implementação manual
V2 atualOKLCh com interpolação de luminosidadeConsistência perceptual real; dark mode algorítmico; txtOn automático com WCAG

A mudança para OKLCh foi a maior ruptura técnica entre V1 e V2. No espaço OKLCh:

  • A percepção de brilho é linear (L=0.5 parece de fato "meio brilhante" para o olho humano)
  • O hue se mantém estável durante tints e shades (azul não vira roxo ao clarear)
  • O croma pode ser reduzido independentemente sem distorção de matiz

Referências