Pular para o conteúdo principal

Guia de Configuração de Temas

Premissa

No Aplica DS, toda decisão visual começa em configuração. Cada marca é definida em um arquivo .config.mjs — não no Figma, não em JSON manual. O engine lê essa configuração e gera automaticamente todos os tokens para cada combinação de mode, surface e dimension.

Este guia cobre como criar e configurar temas de marca e foundations usando o pacote @aplica/aplica-theme-engine.


Visão Geral: Temas vs Foundations

Esses dois conceitos têm responsabilidades distintas e se complementam:

ConceitoO que defineOnde ficaO que gera
TemaIdentidade visual: cores, tipografia, gradientestheme-engine/config/<nome>.config.mjsdata/brand/<nome>/ com paletas decompostas
FoundationAliases para os tokens semânticostheme-engine/config/foundations/<nome>.config.mjsdata/foundation/<nome>/ com tokens de consumo

Um tema pode ter múltiplas foundations, mas cada tema é vinculado a uma foundation no build. A foundation expõe os tokens com nomes simples (foundation.bg.primary, foundation.spacing.medium) que apontam para a camada semântica do tema configurado.


Estrutura do Config de Tema

Os arquivos de config de marca usam defineThemeEngineConfig do pacote:

// theme-engine/config/minha-marca.config.mjs
import { defineThemeEngineConfig } from '@aplica/aplica-theme-engine/config';

export default defineThemeEngineConfig({
// ─────────── OBRIGATÓRIO ───────────
name: 'minha_marca', // ID do tema; define o nome da pasta em data/brand/

colors: { // Todas as cores hex usadas pelo tema
brand_principal: '#E7398A',
brand_secundaria: '#38C2D0',
brand_terciaria: '#8F58BD',
acao_primaria: '#C40145',
acao_secundaria: '#1872A6',
acao_link: '#FF0F80',
// ... feedback e product (ver seção abaixo)
},

mapping: { // Mapeia conceitos semânticos → chaves de cores
brand: {
first: 'brand_principal',
second: 'brand_secundaria',
third: 'brand_terciaria'
},
interface: {
function: {
primary: 'acao_primaria',
secondary: 'acao_secundaria',
link: 'acao_link'
},
feedback: {
info_default: 'feedback_info',
info_secondary: 'feedback_info_dark',
success_default: 'feedback_success',
// ... warning, danger
}
},
product: {
promo_default: 'produto_promo',
promo_secondary: 'produto_promo_alt',
// ... cashback, premium
}
},

typography: { // Famílias e pesos de fonte
fontFamilies: {
main: 'Inter',
content: 'Inter',
display: 'Inter',
code: 'Fira Code'
},
weights: {
main: {
light: { normal: 300, italic: 300 },
regular: { normal: 400, italic: 400 },
semibold: { normal: 600, italic: 600 },
bold: { normal: 700, italic: 700 },
black: { normal: 900, italic: 900 }
}
// content, display, code têm a mesma estrutura
}
},

// ─────────── OPCIONAL ───────────
options: { /* ver seção de opções */ },
gradients: { /* ver seção de gradientes */ }
});

Cores e Mapeamento Semântico

A separação entre colors e mapping é deliberada:

  • colors — um dicionário simples de nomes livres para valores hex. O nome é interno, pode ser qualquer string descritiva.
  • mapping — conecta esses nomes livres aos papéis semânticos fixos do sistema.

Essa separação permite ter vários temas compartilhando os mesmos papéis semânticos com cores completamente diferentes, sem duplicar a lógica de mapeamento.

Cores de Feedback

Cada tipo de feedback tem uma variante default (mais suave, para backgrounds) e secondary (mais saturada, para bordas e ícones):

colors: {
// Feedback — suave para bg, saturado para borda/ícone
info_azul: '#CBF6ED', // default: suave
info_azul_sat: '#1872A6', // secondary: saturado
ok_verde: '#D7F6CB',
ok_verde_sat: '#86C46D',
aviso_amber: '#FEE6C2',
aviso_sat: '#FDB750',
erro_vermelho: '#F9C8C8',
erro_sat: '#EE5A5A',
},

mapping: {
interface: {
feedback: {
info_default: 'info_azul',
info_secondary: 'info_azul_sat',
success_default: 'ok_verde',
success_secondary: 'ok_verde_sat',
warning_default: 'aviso_amber',
warning_secondary: 'aviso_sat',
danger_default: 'erro_vermelho',
danger_secondary: 'erro_sat'
}
}
}

Cores de Product

Mesma lógica: default + secondary por item. O schema padrão inclui promo, cashback e premium:

colors: {
promo_cor: '#6BC200',
promo_alt: '#D2FD9D',
cashback_cor: '#FFBB00',
cashback_alt: '#FFF94F',
premium_cor: '#B200AF',
premium_alt: '#EBC2DD'
},

mapping: {
product: {
promo_default: 'promo_cor',
promo_secondary: 'promo_alt',
cashback_default: 'cashback_cor',
cashback_secondary: 'cashback_alt',
premium_default: 'premium_cor',
premium_secondary: 'premium_alt'
}
}

Lembre-se do custo: Cada item de product gera dezenas de tokens por camada por tema. Antes de adicionar um item novo, verifique se feedback ou brand existentes não resolvem o caso. Veja 01-colors.md para o racional completo.


Opções de Configuração

A chave options controla o comportamento do engine para esse tema:

Estratégias de texto (txtOnStrategy)

ValorComportamento
'high-contrast' (padrão)Sempre preto ou branco — máximo contraste
'brand-tint'Usa o tom da paleta que passa WCAG — mantém cor de marca
'custom-tint'Cor fixa configurada, com fallback se falhar WCAG
options: {
txtOnStrategy: 'brand-tint',

// Apenas para 'custom-tint':
txtOnCustomTint: {
light: '#1a1a2e', // texto sobre superfícies claras
dark: '#f0f0ff', // texto sobre superfícies escuras
fallback: 'high-contrast' // fallback se a cor não passar WCAG
}
}

Opções avançadas do brand-tint (desde v3.18)

Quando txtOnStrategy: 'brand-tint' está ativo, duas opções complementares controlam o comportamento do walk de paleta:

colorContrastDecompose — direção do walk e extremo de fallback

ValorComportamento
'startDark' (padrão)Percorre a paleta priorizando tons escuros; extremo = preto (#000000)
'startLight'Percorre a paleta priorizando tons claros; extremo = branco (#ffffff)

O valor também determina qual extremo (preto ou branco) é usado quando overlap está ativo.

overlap — ignorar o walk em níveis saturados

options: {
txtOnStrategy: 'brand-tint',
colorContrastDecompose: 'startDark',

// Superfícies de paleta no nível ≥ 120 pulam o walk e vão direto
// para o extremo definido por colorContrastDecompose.
// Use quando cores muito saturadas tornam o texto "tintado" menos legível
// do que preto ou branco puro.
overlap: { level: 120 }
}

Superfícies abaixo do threshold usam o walk normal de brand-tint. Superfícies no nível ou acima saltam diretamente para o extremo (preto se startDark, branco se startLight). O fallback WCAG é preservado — acessibilidade nunca é silenciosamente quebrada.

Referência: aplica_ember demonstra a combinação completa — colorContrastDecompose: 'startDark' + overlap: { level: 120 } — para um tema culinário onde texto preto em superfícies laranja saturadas é mais editorial e legível do que um tint da paleta.

Outras opções relevantes

PropriedadePadrãoDescrição
darkModeChroma0.85Fator de saturação no dark mode (0.7 = mais suave, 1.0 = igual ao light)
accessibilityLevel'AA'Nível WCAG mínimo: 'AA' (4.5:1) ou 'AAA' (7:1)
acceptAALevelFallbacktrueAo visar AAA, aceita AA (4.5:1) se AAA não puder ser atingido
includePrimitivesfalseGera _primitive_theme.json com a paleta bruta decomposta (todos os 19 níveis × todas as cores declaradas). Habilite quando o arquivo Figma usa Figma Variables diretamente — não via Tokens Studio — e precisa acessar valores primitivos de paleta. Desabilitado por padrão desde 3.6.3 porque a maioria dos workflows precisa apenas das camadas semântica e foundation.
uiTokensfalseGera _ui.json com tokens UI com escopo de componente
borderOffset.palette10Distância da borda em relação ao surface (escala 10–190)
borderOffset.neutrals1Passos de distância na escala de neutrals
txtBaseColorLevelpadrão do workspaceNível de paleta inicial para busca do token txt neste tema (sobrescreve generation.colorText.txtBaseColorLevel do workspace)

Workspace Config: generation.colorText (desde 3.6.0)

A geração do token txt e dos aliases de texto legível é controlada no nível do workspace em aplica-theme-engine.config.mjs, não por tema. Isso garante que todos os temas compartilhem o mesmo contrato de texto:

import { defineThemeEngineConfig } from '@aplica/aplica-theme-engine/config';

export default defineThemeEngineConfig({
generation: {
colorText: {
generateTxt: true, // Habilita a propriedade txt (default: false para compatibilidade)
txtBaseColorLevel: 140, // Nível de paleta inicial para txt (sobe se necessário para WCAG)
fallbackBaseColorLevel: 160, // Nível de fallback secundário
textExposure: ['feedback'], // Quais famílias recebem aliases foundation.txt.*
// Opções: 'feedback', 'interfaceFunction', 'product'
}
},
paths: {
configDir: './theme-engine/config',
globalConfigDir: './theme-engine/config/global',
foundationsDir: './theme-engine/config/foundations',
dataDir: './data',
distDir: './dist'
}
});

Valores de textExposure

ValorAliases de foundation gerados
'feedback'foundation.txt.info, .success, .warning, .danger (padrão)
'interfaceFunction'foundation.txt.primary, .secondary, .link
'product'Itens de product por tema: foundation.txt.promo, .cashback, etc.

Breaking change (3.6.0 → 3.6.1): Na 3.6.0, algumas opções relacionadas a txt ficavam por tema. Na 3.6.1, foram movidas para generation.colorText no workspace config. Se estiver atualizando da 3.6.0, mova generateTxt, txtBaseColorLevel, fallbackBaseColorLevel e textExposure dos configs de tema individuais para o workspace config.

Veja 07-txt-token.md para a documentação completa do contrato txt.


Checklist de sincronização de workspace

Antes de publicar ou fazer deploy de múltiplos temas, verifique que todas as configurações de workspace estão consistentes. Algumas opções afetam as camadas compartilhadas de arquitetura (mode/, surface/, semantic/) — um único tema divergente corrompe o output para todos os consumidores.

ConfiguraçãoEscopoRegra
options.interaction.legacyStructureWorkspaceDeve ser idêntico em todos os temas. Controla se as camadas compartilhadas emitem grupos solid/ghost.
options.interaction.decomposition.methodWorkspaceDeve ser idêntico. Misturar system-scale e dilution entre temas quebra a camada semântica.
generation.colorText.*WorkspaceConfigurado uma vez em aplica-theme-engine.config.mjs, não por tema.
options.interaction.decomposition.modeResolutionPor temaPode diferir entre temas. Afeta apenas o comportamento de polaridade de superfície do feedback daquele tema.
options.baseAdaptationPor temaPode diferir entre temas. Afeta apenas as superfícies normal/default daquele tema.
options.txtOnStrategyPor temaPode diferir entre temas.
options.darkModeChromaPor temaPode diferir entre temas.
options.accessibilityLevelPor temaPode diferir entre temas.

Verificação rápida antes do deploy:

# Confirmar que todos os temas declaram o mesmo valor de legacyStructure
grep -r "legacyStructure" theme-engine/config/

# Confirmar que todos os temas declaram o mesmo método de decomposição
grep -r "method:" theme-engine/config/

# Validar o alinhamento de schema em todos os temas sem gravar
npm run sync:architecture:test

Decomposição de Interação (desde 3.9.0)

Por padrão, interface.function e interface.feedback geram estados de interação (normal, action, active, focus) usando a lógica de níveis de paleta do engine. A versão 3.9.0 introduz controle autoral sobre como esses estados são derivados.

Modo de decomposição

Configure options.interaction.decomposition.method no config do tema:

ValorComportamento
'system-scale' (padrão)Comportamento legado, agora nomeado explicitamente. Níveis de paleta controlam os estados (ex.: active: 120). Todos os temas existentes usam isso.
'dilution'Novo. Os estados de cor movem a cor base em direção ao branco ou preto sem rotacionar o hue. Fatores controlam a intensidade (ex.: active: 0.8, action: 1.2). Valores acima de 1.0 invertem a direção.

Quando usar cada método:

CenárioMétodoTargetPor quê
Temas existentes, sem mudança visual necessáriasystem-scaleTotalmente retrocompatível; níveis de paleta são previsíveis e explícitos
Botões que se adaptam naturalmente ao contexto light/darkdilution + target: 'canvas'Estados se diluem em direção ao branco em canvas claro e preto em canvas escuro — sem override manual para dark mode
Cor de marca deve permanecer cromática em estados hover/activedilution + target: 'anchor'source: 'palette'Mantém os estados de interação dentro da mesma família de hue em vez de derivar para cinza
Cor de acento customizada para estados em todas as superfíciesdilution + target: 'anchor'source: 'hex'Fixa o destino da diluição em uma cor específica independente do tema
Estados que referenciam outro token geradodilution + target: 'anchor'source: 'token'Vincula os estados a um token vivo — atualiza automaticamente quando a cor referenciada muda
function e feedback precisam de regras diferentesQualquer método por grupoUse options.interaction.groups para configurar cada um independentemente (desde 3.12.0)
options: {
interaction: {
decomposition: {
method: 'dilution',
// direction: 'high' // 'high' = mover para mais escuro (padrão); 'low' = mais claro
},
surfaces: {
solid: {
levels: {
// dilution: fatores (1.0 = cor base, <1.0 = menos diluído, >1.0 = inverte direção)
action: 1.2,
active: 0.8,
focus: 0.3,
}
},
ghost: {
enabled: true,
levels: {
// ghost usa níveis de paleta mesmo em modo dilution
action: 40,
active: 60,
focus: 20,
}
}
}
}
}

normal sempre permanece na cor base autoral — nenhum override é esperado para ele.

ghostNormalTxtOnStrategy — texto no estado ghost normal (desde v3.15.1)

O estado ghost.normal tem background transparente — o texto legível (txtOn) precisa ser calculado contra o canvas de página, não contra uma cor sólida. ghostNormalTxtOnStrategy controla como esse cálculo é feito:

ValorComportamento
'txt' (padrão)Usa a cor de texto interativo acessível da paleta contra o canvas — o mesmo valor calculado para txt[normal]. Mais consistente e previsível.
'surface'Executa txtOnStrategy (high-contrast / custom-tint / brand-tint) contra o canvas background — era o comportamento implícito antes da v3.15.1.
options: {
interaction: {
surfaces: {
ghost: {
ghostNormalTxtOnStrategy: 'txt' // padrão
}
},
// Override por grupo (ex: apenas feedback usa 'surface')
groups: {
feedback: {
surfaces: {
ghost: {
ghostNormalTxtOnStrategy: 'surface'
}
}
}
}
}
}

Use 'surface' quando a cor de texto ghost precisar seguir a lógica de txtOnStrategy do tema (ex: manter brand tint no estado ghost). Use 'txt' (padrão) para comportamento mais previsível que não depende da paleta de marca.

Compatibilidade retroativa

  • Temas sem config interaction geram exatamente como antes — system-scale é o padrão implícito.
  • options.interfaceFunctionPaletteLevels legado ainda funciona e é mapeado internamente para options.interaction.surfaces.solid.levels.
  • Estruturas existentes de background, txtOn, border e txt permanecem inalteradas.

Destinos de dilution (desde 3.9.0 / 3.13.1)

Ao usar method: 'dilution', a propriedade target controla para qual destino os estados se diluem:

ValorComportamento
'canvas' (padrão para dilution)Move em direção ao canvas resolvido para o quadrante ativo — clareia em canvases claros, escurece em canvases escuros. fallback controla o comportamento quando nenhum canvas é resolvível.
'anchor' (desde 3.13.1)Move em direção a uma âncora cromática configurável. Os estados se deslocam em hue em direção à âncora em vez de ao branco/preto.

Canvas-aware dilution (desde 3.9.0)

options: {
interaction: {
decomposition: {
method: 'dilution',
target: 'canvas', // padrão quando method é 'dilution'
fallback: 'ambient-neutral', // 'legacy' | 'ambient-neutral'
}
}
}

Anchor-aware dilution (desde 3.13.1–3.13.3)

options: {
interaction: {
decomposition: {
method: 'dilution',
target: 'anchor',
anchor: {
source: 'palette', // 'palette' | 'hex' | 'token'
// hex: '#7C3AED', // obrigatório quando source: 'hex'
// token: 'brand.branding.first.default.background', // obrigatório quando source: 'token'
canvasAware: true, // âncora clareia em canvases claros, escurece em canvases escuros
canvasMix: 0.2 // 0.0–1.0 — intensidade da resposta da âncora ao canvas
}
}
}
}
anchor.sourceDescrição
'palette'Família de paleta atual da marca — permanece dentro da mesma família de cores
'hex'Destino cromático fixo declarado em anchor.hex
'token'Outra escala gerada declarada em anchor.token

anchor.canvasAware + anchor.canvasMix permitem que a âncora mantenha seu hue próprio enquanto ainda clareia em canvases claros e escurece em canvases escuros. Use target: 'anchor' para estados secundários mais brilhantes ou mais cromáticos que devem permanecer sistêmicos sem overrides manuais.

A configuração de target / anchor se aplica por tema, por surface e por grupo — qualquer branch que use method: 'dilution' pode configurar seu destino de forma independente.


Configuração por grupo (desde 3.12.0)

Quando function e feedback precisam de comportamento de decomposição diferente, declare-os em options.interaction.groups:

options: {
interaction: {
decomposition: { method: 'system-scale' }, // padrão do tema
groups: {
function: {
decomposition: { method: 'dilution' }, // override apenas para function
surfaces: {
solid: {
levels: { action: 1.2, active: 0.8, focus: 0.3 }
}
}
},
feedback: {
// herda o padrão do tema (system-scale)
surfaces: {
solid: {
levels: { action: 80, active: 120, focus: 50 }
}
}
}
}
}
}

Ordem de resolução: padrão do tema → nível de surface → nível de grupo → nível grupo-surface (cada camada sobrescreve a anterior).

groups.{function|feedback}.levels não é suportado. Declare valores de estado em groups.{function|feedback}.surfaces.solid.levels ou .ghost.levels.

Resolução de modo para feedback (desde 3.13.6)

Por padrão, os tokens de feedback espelham a polaridade de quadrante: a superfície dark-positive é o inverso de light-positive, e dark-negative espelha light-negative. Esse é o modeResolution: 'quadrant'.

Alguns produtos querem que o feedback siga apenas a semântica light/dark — mesma cor de superfície em positive e negative dentro do mesmo modo. Use modeResolution: 'mode' para isso:

options: {
interaction: {
decomposition: {
method: 'dilution', // modeResolution funciona com ambos os métodos
modeResolution: 'mode', // 'quadrant' (padrão) | 'mode'
}
}
}
ValorComportamento de superfícieTexto e borda
'quadrant' (padrão)Superfícies positive e negative são espelhadas por quadrante — dark-positive é o inverso de light-positiveTexto e bordas seguem a mesma inversão de quadrante
'mode'Superfícies positive e negative são idênticas dentro do mesmo modo — sem inversão de polaridadeTexto e bordas alinham com a superfície (sem inversão)

Quando usar 'mode':

  • O feedback (erro, aviso, sucesso) deve parecer idêntico em contextos de superfície positive e negative
  • O estado de erro do produto é puramente informacional e não deve variar por polaridade de superfície

Quando manter 'quadrant' (padrão):

  • As cores de feedback precisam se adaptar ao contexto de fundo — uma superfície negative de canvas escuro precisa de tratamento distinto
  • Dilution com âncora está configurado e a âncora é específica por quadrante

modeResolution também pode ser definido por grupo: options.interaction.groups.feedback.decomposition.modeResolution: 'mode' — aplica-se apenas ao feedback enquanto function mantém sua própria estratégia.

Regra de consistência do workspace

Todos os temas no mesmo workspace devem concordar em options.interaction.legacyStructure:

options: {
interaction: {
legacyStructure: false, // deve ser idêntico em todos os temas do workspace
}
}
ValorForma pública do output
true (padrão)Forma pública anterior — grupos function e feedback como antes
falseGrupos solid e ghost explícitos gerados em brand, mode, surface e semantic

Workspaces mistos quebram. mode, surface e semantic são camadas compartilhadas — se alguns temas tiverem legacyStructure: false e outros não, essas camadas ficam inconsistentes. Defina esse flag de forma idêntica em todos os configs de tema.

Adaptação de base por quadrante (desde 3.13.4)

Contexto — o que é um quadrante? Cada tema gera quatro variantes determinadas por dois eixos independentes: modo claro vs escuro, e contexto de superfície positivo vs negativo. As quatro combinações são light-positive, light-negative, dark-positive, dark-negative. Juntas, elas formam o quadrante ativo.

Por padrão, superfícies normal de interação e superfícies default de produto são fixas na cor base autoral, independente do quadrante ativo. Isso significa que um botão primário tem a mesma aparência em light-positive e em dark-positive — o hex autoral do designer é a referência estável em todos os quatro contextos.

Ative a adaptação por quadrante por tema:

options: {
baseAdaptation: true // interaction normal + product default respondem ao quadrante ativo
}

Efeito concreto: dado um tema com action_primary: '#C40145':

QuadrantebaseAdaptation: false (padrão)baseAdaptation: true
light-positive#C40145#C40145 (inalterado — light-positive é a linha de base)
light-negative#C40145tom levemente mais claro
dark-positive#C40145tom adaptado para escuro
dark-negative#C40145tom mais escuro adaptado

A adaptação ajusta a luminosidade dentro do espaço OKLCh preservando hue e croma — a família de cor permanece a mesma.

Tokens afetados: apenas as famílias interface.function.*.normal.background e product.*.default.background. Todos os outros estados (action, active, focus) e todos os valores de txtOn/border/txt não são afetados.

Quando usar:

  • O botão primário é difícil de ver em dark-negative porque sua cor autoral está muito próxima do canvas
  • A marca explicitamente quer que as superfícies base se adaptem ao contexto (marcas expressivas, alto dinamismo visual)

Quando não usar:

  • Produtos financeiros ou institucionais onde a cor de marca deve permanecer uma referência fixa e reconhecível
  • Quando normal é usado como âncora de correspondência de cor na UI (ex.: o botão combina com o logotipo da marca)

Por tema, não de workspace. Ao contrário de legacyStructure, baseAdaptation é independente por tema — temas no mesmo workspace podem misturar true e false livremente sem corromper as camadas compartilhadas.


Sobreposições (Overrides)

Overrides permitem substituir valores gerados pelo engine em casos específicos:

overrides: {
// Substituir grayscale (temperatura quente)
grayscale: {
light: {
surface: { '5': '#faf8f5', '10': '#f5f2ee', '140': '#1a1814' },
txtOn: { /* ... */ },
border: { /* ... */ }
},
dark: { /* mesma estrutura */ }
},

// Substituir neutrals de uma cor específica
neutrals: {
brand_principal: {
light: { surface: { '5': '#fdf4f9' } },
dark: { surface: { '5': '#1a0d14' } }
}
}
}

Regra: Overrides são a última alternativa após esgotar as opções semânticas padrão. Valide com theme-engine sync:architecture:test após aplicar overrides para detectar incompatibilidades de referência antes de um build completo.

Overrides dinâmicos de estado de interação (desde 3.14.5)

overrides.interaction substitui o formato legado overrides.interface.function.*. As cores de override passam pelo pipeline de decomposição completo — dilution, base adaptation, espelhamento de quadrante e texto de acessibilidade — em vez de serem aplicadas como patches brutos.

overrides: {
interaction: {
// Nível de grupo: aplica-se a TODOS os itens do grupo
function: {
states: {
active: { color: '#0067FF' } // todos os itens de function, estado active
}
},

// Nível de item: aplica-se a um item específico em seus estados
feedback: {
items: {
warning_default: {
states: {
normal: { color: '#FF9900' }
}
}
}
}
}
}

Hierarquia de targeting — o mais específico vence:

TargetCaminhoExemplo
Todos os itens, todos os presets, um estadointeraction.{group}.states.{state}function.states.active
Todos os itens, um preset, um estadointeraction.{group}.states.{state}.{preset}function.states.active.solid
Um item, todos os presets, um estadointeraction.{group}.items.{item}.states.{state}feedback.items.warning_default.states.normal
Um item, um preset, um estadointeraction.{group}.items.{item}.states.{state}.{preset}feedback.items.warning_default.states.normal.ghost

Campos disponíveis no leaf de estado (v3.20+):

CampoO que fazChecagem WCAG
backgroundSubstitui a surface gerada pelo hex declarado diretamente. Engine auto-computa txtOn (WCAG AA) e deriva border a partir desse hex. Use quando uma cor de marca deve aparecer diretamente num estado sem pipeline de dilução.txtOn auto sempre passa AA
colorPassa o hex pelo pipeline de decomposição completo como cor-fonte do estado. A surface é diluída, não é o hex bruto. Use para redirecionar um estado a outra cor de marca mantendo o comportamento de diluição.derivado por estratégia
txtOnSobrepõe o texto/ícone sobre a surface (solid) ou canvas (ghost transparente). Só é aplicado se passar WCAG AA. Se reprovar, o engine emite [override:accessibility] com o ratio exato e preserva o valor auto-computado.enforced
txtSobrepõe o texto de leitura ambiente. Só relevante quando generateTxt: true está ativo no workspace config.enforced
borderSobrepõe a cor de borda incondicionalmente. Sem checagem de contraste.nenhuma

Cada leaf deve declarar pelo menos um campo — um leaf vazio lança erro em parse time.

// Exemplos de combinações válidas
{ background: '#C73C9E' } // surface direta; txtOn e border automáticos
{ color: '#0067FF' } // redireciona via pipeline de dilução
{ txtOn: '#FFFFFF', border: '#D0D5DD' } // só texto e borda, sem alterar surface
{ color: '#FF0000', txtOn: '#FFFFFF' } // cor + texto fixo explícito

Nomes de estado aceitos: normal, action, active, focus

Nota v3.21.0: antes da v3.21, quando apenas background era declarado sem txtOn explícito, o engine derivava o txtOn usando a paleta da cor original do item (hue incorreto). A partir da v3.21, o txtOn é derivado da paleta gerada a partir do próprio hex declarado como background. Se precisar de um tom específico, declare txtOn explicitamente — o valor explícito tem sempre prioridade sobre o auto-computado.

Formato por modo (desde v3.22.0):

Desde v3.22.0, preset leaves aceitam valores diferentes por modo. Três formatos disponíveis — todos retrocompatíveis:

// Global — mesmo valor em ambos os modos (comportamento existente, sem mudança)
active: {
solid: { background: '#C73C9E' }
}

// Modo-específico duplo — valor diferente por modo
active: {
solid: {
light: { background: '#C73C9E' },
dark: { background: '#AD2385' }
}
}

// Modo-específico parcial — um modo explícito, o outro auto-gerado
active: {
solid: {
light: { background: '#C73C9E' }
// dark omitido → auto-gerado pelo engine
}
}

Regra de detecção: se o valor de um preset contiver chave light ou dark, é tratado como modo-específico. Do contrário, é tratado como leaf global. Consistente com a convenção { light, dark } já usada em overrides.grayscale, overrides.neutrals e options.txtOnCustomTint.

Antes dessa release, a única forma de ter cores diferentes no dark mode era declarar um override global e aceitar que a mesma cor (muitas vezes otimizada para light) fosse forçada no dark mode — gerando warnings [override:accessibility] que o engine então contornava silenciosamente. Leaves por modo eliminam essa classe de warning inteiramente.

Chaves de itens de function: primary, secondary, link

Chaves de itens de feedback: info_default, info_secondary, success_default, success_secondary, warning_default, warning_secondary, danger_default, danger_secondary

Migração: O formato legado overrides.interface.function.* ainda é aceito como alias de compatibilidade para overrides.interaction.function.states.*. Novos configs devem usar o caminho overrides.interaction.


Gradientes

Gradientes são controlados globalmente em themes.config.json e podem ser customizados por tema:

// No config do tema — gradientes customizados por cor de marca
gradients: {
brand: {
first: {
angle: 135,
stops: [
{ position: 0, colorRef: 'brand.branding.first.lowest.background' },
{ position: 1, colorRef: 'brand.branding.first.default.background' }
]
},
second: { /* ... */ },
third: { /* ... */ }
}
}

Quando omitido, o engine usa um gradiente sólido como stub. Para desabilitar gradientes no projeto inteiro, configure global.gradients: false no themes.config.json.

Ordem de build com gradientes: Os gradientes só aparecem no output final quando data/semantic/default.json tem a seção semantic.color.gradient — criada pelo sync:architecture. Sempre rode npm run tokens:build (pipeline completo) em vez de tokens:build:all isolado quando gradientes estiverem habilitados.


Registrar o Tema no Build

Após criar o .config.mjs, registre o tema no arquivo central de configuração:

// theme-engine/config/global/themes.config.json
{
"themes": {
"minha_marca": {
"includePrimitives": true,
"foundation": {
"brand": "engine",
"files": [
"default.json",
"styles/typography_styles.json",
"styles/elevation_styles.json"
]
}
}
}
}

Regra crítica: A chave em themes deve ser idêntica ao name definido no config do tema e ao nome da pasta gerada em data/brand/.


Pipeline de Geração

Pipeline completo (recomendado)

npm run tokens:build

Executa automaticamente na ordem correta:

  1. ensure:data — garante que a estrutura de data/ existe
  2. dimension:generate — gera escala dimensional
  3. themes:generate — decompõe cores e gera data/brand/<tema>/
  4. sync:architecture — propaga referências para mode, surface, semantic e foundation
  5. foundations:generate — gera aliases Foundation
  6. build:all — Style Dictionary → dist/ (JSON, CSS, ESM, CJS, TypeScript)

Comandos individuais úteis

ComandoQuando usar
npm run tokens:themesApós alterar cores ou mapeamento de um tema
theme-engine themes:single --config=minha-marcaGerar apenas um tema específico
npm run tokens:syncApós alterar o schema de arquitetura
npm run tokens:foundationsApós alterar um config de foundation
npm run tokens:build:allApenas o build Style Dictionary (quando data/ já está atualizado)
theme-engine validate:dataVerificar integridade de data/ antes de buildar

Splitting de bundles de marca para Figma (desde 3.14.0)

Quando figma:generate é executado, o engine avalia a contagem de tokens em cada bundle de marca. Se o bundle exceder o limite de tokens por coleção do Figma (padrão 5.000), ele divide automaticamente _brand.json em chunks semânticos:

Arquivo de chunkConteúdo
_brand_product.jsonTokens semânticos de produto/carreira
_brand_text.jsonTokens de texto
_brand_interface.jsonTokens de function e feedback de interface
_brand_core.jsonTokens de marca central (quando necessária divisão adicional)

Isso é completamente automático — nenhuma mudança de config é necessária. O contrato lógico de tokens e o output em dist/ não mudam; a divisão afeta apenas os arquivos intermediários em data/brand/ que o Tokens Studio lê.

Para sobrescrever o limite de tokens (ex.: se seu plano do Figma tem um orçamento de coleção diferente):

APLICA_THEME_ENGINE_BRAND_TOKEN_LIMIT=3000 npm run tokens:figma

Workspaces com esquemas de produto grandes (muitos tipos de carreira, muitas cores de produto) têm maior probabilidade de acionar a divisão.

Dica de ouro — performance de tokens de marca. Se o bundle de marca no Figma ultrapassar aproximadamente 3.000 tokens, o seletor de variáveis fica lento e a sincronização trava. O engine divide automaticamente o _brand.json desde a 3.14.0, o que normalmente resolve. Se a performance ainda degradar após a divisão, considere: (1) quebrar o workspace em repositórios por marca; ou (2) desabilitar a superfície negative (options.surfaces: ['positive']) — isso reduz o tamanho do arquivo de marca em 50%, e é adequado apenas quando o produto nunca renderiza em fundos com superfície negativa.

Quando rodar sync:architecture

O sync propaga referências entre camadas. Rode quando:

  • Alterar o schema de arquitetura (adicionar/remover items de feedback ou product)
  • Adicionar um novo tema e precisar de referências semânticas atualizadas
  • Gradientes estão habilitados e você rodou themes:generate
  • Após pull de mudanças que alteraram a estrutura do schema

Nunca edite manualmente data/mode/*.json, data/surface/*.json, data/semantic/default.json ou data/foundation/engine/default.json. O sync sobrescreve qualquer edição manual nesses arquivos.


Adicionando uma Nova Foundation

Quando o conjunto padrão de aliases não é suficiente para um consumidor específico:

  1. Crie theme-engine/config/foundations/minha-foundation.config.mjs (baseie-se em engine.config.mjs)
  2. Defina name, outputPath, structure (seções e itens) e references (mapeamento para Semantic)
  3. Execute npm run tokens:foundations
  4. Vincule ao tema em themes.config.json: "foundation": { "brand": "minha-foundation", ... }

Grupos disponíveis em structure

A chave structure aceita os seguintes grupos. Cada grupo é opcional — inclua apenas o que o consumidor precisa expor:

GrupoO que expõeExemplo de item
bgSuperfícies de cor (background, border, txtOn, txt)primary, secondary, feedback.success
borderTokens de borda derivados de superfícieprimary
txtTokens de texto legível sobre canvasprimary
opacityTokens de opacidade (surface.opacity.*)light, dark
spacingEscala dimensional de espaçamentosmall, medium, large
sizingEscala dimensional de tamanhosicon, avatar
borderWidthEspessura de borda (semantic.border.width.*) — 5 níveisnone, small, medium, large, extraLarge
borderRadiusRaio de borda (semantic.border.radii.*) — 9 níveisstraight, small, medium, circular
typographyEstilos tipográficos compostosheading, body, action
gradientGradientes de marcaprimary, secondary
// config/foundations/minha-foundation.config.mjs → structure:
structure: {
borderWidth: {
items: ['none', 'small', 'medium', 'large', 'extraLarge']
},
borderRadius: {
items: ['straight', 'micro', 'extraSmall', 'small', 'medium', 'large', 'extraLarge', 'mega', 'circular']
},
bg: {
items: ['primary', 'secondary']
}
}

borderWidth e borderRadius são frequentemente omitidos por engano. Os tokens semantic.border.width.* e semantic.border.radii.* já existem no output semântico — eles só precisam ser declarados no structure da foundation para ficarem disponíveis como aliases simples (foundation.borderWidth.medium, foundation.borderRadius.circular). Nenhuma mudança no engine é necessária.

Alias pass genérico (desde v3.23.0)

Qualquer chave em structure que não seja um dos grupos acima pode declarar três campos para expor tokens semânticos como aliases de foundation sem precisar de mudanças no engine:

CampoO que faz
semanticPathPath base na camada semântica. O item é concatenado: semanticPath.item
typeTipo do token para Tokens Studio / Style Dictionary
itemsArray de nomes dos tokens a expor
// Expõe semantic.typography.fontFamilies.* como foundation.fontFamilies.*
fontFamilies: {
semanticPath: 'semantic.typography.fontFamilies',
type: 'fontFamilies',
items: ['main', 'content', 'display', 'code']
}

Gera em CSS:

--foundation-fontFamilies-main: var(--semantic-typography-fontFamilies-main);
--foundation-fontFamilies-content: var(--semantic-typography-fontFamilies-content);
--foundation-fontFamilies-display: var(--semantic-typography-fontFamilies-display);
--foundation-fontFamilies-code: var(--semantic-typography-fontFamilies-code);

Tipos comuns para o campo type:

O que exportypesemanticPath provável
Famílias tipográficasfontFamiliessemantic.typography.fontFamilies
Tamanhos de fontefontSizessemantic.dimension.fontSizes
Alturas de linhalineHeightssemantic.typography.lineHeights
Pesos de fontefontWeightssemantic.typography.fontWeights
Espaçamento de letrasletterSpacingsemantic.typography.letterSpacing
Qualquer dimensão customdimensionpath específico do workspace

Pré-requisito: o path declarado em semanticPath deve existir em data/semantic/default.json. O mecanismo cria aliases — não gera valores.

Atenção: borderWidth e borderRadius têm handlers dedicados e não usam a sintaxe genérica — funcionam com items apenas (como documentado acima).


Referência Rápida

TarefaAção
Criar novo temaAdicionar .config.mjs em theme-engine/config/ + registrar em themes.config.json + npm run tokens:build
Alterar cores de um temaEditar o .config.mjs do tema + npm run tokens:themes + npm run tokens:build:all
Alterar foundationEditar .config.mjs em theme-engine/config/foundations/ + npm run tokens:foundations + npm run tokens:build:all
Alterar schema (feedback/product)Editar override de schema em theme-engine/schemas/ + npm run tokens:sync + rebuild
Gradientes não aparecem no outputRodar npm run tokens:sync antes de tokens:build:all
Verificar sem gravartheme-engine sync:architecture:test
Ver schema atualtheme-engine sync:architecture:schema

Referências