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:
| Conceito | O que define | Onde fica | O que gera |
|---|---|---|---|
| Tema | Identidade visual: cores, tipografia, gradientes | theme-engine/config/<nome>.config.mjs | data/brand/<nome>/ com paletas decompostas |
| Foundation | Aliases para os tokens semânticos | theme-engine/config/foundations/<nome>.config.mjs | data/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)
| Valor | Comportamento |
|---|---|
'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
| Valor | Comportamento |
|---|---|
'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
| Propriedade | Padrão | Descrição |
|---|---|---|
darkModeChroma | 0.85 | Fator 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) |
acceptAALevelFallback | true | Ao visar AAA, aceita AA (4.5:1) se AAA não puder ser atingido |
includePrimitives | false | Gera _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. |
uiTokens | false | Gera _ui.json com tokens UI com escopo de componente |
borderOffset.palette | 10 | Distância da borda em relação ao surface (escala 10–190) |
borderOffset.neutrals | 1 | Passos de distância na escala de neutrals |
txtBaseColorLevel | padrão do workspace | Ní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
| Valor | Aliases 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
txtficavam por tema. Na 3.6.1, foram movidas parageneration.colorTextno workspace config. Se estiver atualizando da 3.6.0, movagenerateTxt,txtBaseColorLevel,fallbackBaseColorLeveletextExposuredos 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ção | Escopo | Regra |
|---|---|---|
options.interaction.legacyStructure | Workspace | Deve ser idêntico em todos os temas. Controla se as camadas compartilhadas emitem grupos solid/ghost. |
options.interaction.decomposition.method | Workspace | Deve ser idêntico. Misturar system-scale e dilution entre temas quebra a camada semântica. |
generation.colorText.* | Workspace | Configurado uma vez em aplica-theme-engine.config.mjs, não por tema. |
options.interaction.decomposition.modeResolution | Por tema | Pode diferir entre temas. Afeta apenas o comportamento de polaridade de superfície do feedback daquele tema. |
options.baseAdaptation | Por tema | Pode diferir entre temas. Afeta apenas as superfícies normal/default daquele tema. |
options.txtOnStrategy | Por tema | Pode diferir entre temas. |
options.darkModeChroma | Por tema | Pode diferir entre temas. |
options.accessibilityLevel | Por tema | Pode 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:
| Valor | Comportamento |
|---|---|
'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ário | Método | Target | Por quê |
|---|---|---|---|
| Temas existentes, sem mudança visual necessária | system-scale | — | Totalmente retrocompatível; níveis de paleta são previsíveis e explícitos |
| Botões que se adaptam naturalmente ao contexto light/dark | dilution + 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/active | dilution + 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ícies | dilution + target: 'anchor' | source: 'hex' | Fixa o destino da diluição em uma cor específica independente do tema |
| Estados que referenciam outro token gerado | dilution + target: 'anchor' | source: 'token' | Vincula os estados a um token vivo — atualiza automaticamente quando a cor referenciada muda |
function e feedback precisam de regras diferentes | Qualquer método por grupo | — | Use 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:
| Valor | Comportamento |
|---|---|
'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
interactiongeram exatamente como antes —system-scaleé o padrão implícito. options.interfaceFunctionPaletteLevelslegado ainda funciona e é mapeado internamente paraoptions.interaction.surfaces.solid.levels.- Estruturas existentes de
background,txtOn,borderetxtpermanecem 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:
| Valor | Comportamento |
|---|---|
'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.source | Descriçã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/anchorse aplica por tema, por surface e por grupo — qualquer branch que usemethod: '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}.levelsnão é suportado. Declare valores de estado emgroups.{function|feedback}.surfaces.solid.levelsou.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'
}
}
}
| Valor | Comportamento de superfície | Texto e borda |
|---|---|---|
'quadrant' (padrão) | Superfícies positive e negative são espelhadas por quadrante — dark-positive é o inverso de light-positive | Texto 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 polaridade | Texto 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
modeResolutiontambé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
}
}
| Valor | Forma pública do output |
|---|---|
true (padrão) | Forma pública anterior — grupos function e feedback como antes |
false | Grupos solid e ghost explícitos gerados em brand, mode, surface e semantic |
Workspaces mistos quebram.
mode,surfaceesemanticsão camadas compartilhadas — se alguns temas tiveremlegacyStructure: falsee 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':
| Quadrante | baseAdaptation: false (padrão) | baseAdaptation: true |
|---|---|---|
| light-positive | #C40145 | #C40145 (inalterado — light-positive é a linha de base) |
| light-negative | #C40145 | tom levemente mais claro |
| dark-positive | #C40145 | tom adaptado para escuro |
| dark-negative | #C40145 | tom 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-negativeporque 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 misturartrueefalselivremente 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:testapó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:
| Target | Caminho | Exemplo |
|---|---|---|
| Todos os itens, todos os presets, um estado | interaction.{group}.states.{state} | function.states.active |
| Todos os itens, um preset, um estado | interaction.{group}.states.{state}.{preset} | function.states.active.solid |
| Um item, todos os presets, um estado | interaction.{group}.items.{item}.states.{state} | feedback.items.warning_default.states.normal |
| Um item, um preset, um estado | interaction.{group}.items.{item}.states.{state}.{preset} | feedback.items.warning_default.states.normal.ghost |
Campos disponíveis no leaf de estado (v3.20+):
| Campo | O que faz | Checagem WCAG |
|---|---|---|
background | Substitui 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 |
color | Passa 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 |
txtOn | Sobrepõ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 |
txt | Sobrepõe o texto de leitura ambiente. Só relevante quando generateTxt: true está ativo no workspace config. | enforced |
border | Sobrepõ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
backgroundera declarado semtxtOnexplícito, o engine derivava otxtOnusando a paleta da cor original do item (hue incorreto). A partir da v3.21, otxtOné derivado da paleta gerada a partir do próprio hex declarado comobackground. Se precisar de um tom específico, declaretxtOnexplicitamente — 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 paraoverrides.interaction.function.states.*. Novos configs devem usar o caminhooverrides.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.jsontem a seçãosemantic.color.gradient— criada pelosync:architecture. Sempre rodenpm run tokens:build(pipeline completo) em vez detokens:build:allisolado 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:
ensure:data— garante que a estrutura dedata/existedimension:generate— gera escala dimensionalthemes:generate— decompõe cores e geradata/brand/<tema>/sync:architecture— propaga referências para mode, surface, semantic e foundationfoundations:generate— gera aliases Foundationbuild:all— Style Dictionary →dist/(JSON, CSS, ESM, CJS, TypeScript)
Comandos individuais úteis
| Comando | Quando usar |
|---|---|
npm run tokens:themes | Após alterar cores ou mapeamento de um tema |
theme-engine themes:single --config=minha-marca | Gerar apenas um tema específico |
npm run tokens:sync | Após alterar o schema de arquitetura |
npm run tokens:foundations | Após alterar um config de foundation |
npm run tokens:build:all | Apenas o build Style Dictionary (quando data/ já está atualizado) |
theme-engine validate:data | Verificar 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 chunk | Conteúdo |
|---|---|
_brand_product.json | Tokens semânticos de produto/carreira |
_brand_text.json | Tokens de texto |
_brand_interface.json | Tokens de function e feedback de interface |
_brand_core.json | Tokens 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.jsondesde 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ícienegative(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.jsonoudata/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:
- Crie
theme-engine/config/foundations/minha-foundation.config.mjs(baseie-se emengine.config.mjs) - Defina
name,outputPath,structure(seções e itens) ereferences(mapeamento para Semantic) - Execute
npm run tokens:foundations - 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:
| Grupo | O que expõe | Exemplo de item |
|---|---|---|
bg | Superfícies de cor (background, border, txtOn, txt) | primary, secondary, feedback.success |
border | Tokens de borda derivados de superfície | primary |
txt | Tokens de texto legível sobre canvas | primary |
opacity | Tokens de opacidade (surface.opacity.*) | light, dark |
spacing | Escala dimensional de espaçamento | small, medium, large |
sizing | Escala dimensional de tamanhos | icon, avatar |
borderWidth | Espessura de borda (semantic.border.width.*) — 5 níveis | none, small, medium, large, extraLarge |
borderRadius | Raio de borda (semantic.border.radii.*) — 9 níveis | straight, small, medium, circular |
typography | Estilos tipográficos compostos | heading, body, action |
gradient | Gradientes de marca | primary, 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']
}
}
borderWidtheborderRadiussão frequentemente omitidos por engano. Os tokenssemantic.border.width.*esemantic.border.radii.*já existem no output semântico — eles só precisam ser declarados nostructureda 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:
| Campo | O que faz |
|---|---|
semanticPath | Path base na camada semântica. O item é concatenado: semanticPath.item |
type | Tipo do token para Tokens Studio / Style Dictionary |
items | Array 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 expor | type | semanticPath provável |
|---|---|---|
| Famílias tipográficas | fontFamilies | semantic.typography.fontFamilies |
| Tamanhos de fonte | fontSizes | semantic.dimension.fontSizes |
| Alturas de linha | lineHeights | semantic.typography.lineHeights |
| Pesos de fonte | fontWeights | semantic.typography.fontWeights |
| Espaçamento de letras | letterSpacing | semantic.typography.letterSpacing |
| Qualquer dimensão custom | dimension | path específico do workspace |
Pré-requisito: o path declarado em
semanticPathdeve existir emdata/semantic/default.json. O mecanismo cria aliases — não gera valores.
Atenção:
borderWidtheborderRadiustêm handlers dedicados e não usam a sintaxe genérica — funcionam comitemsapenas (como documentado acima).
Referência Rápida
| Tarefa | Ação |
|---|---|
| Criar novo tema | Adicionar .config.mjs em theme-engine/config/ + registrar em themes.config.json + npm run tokens:build |
| Alterar cores de um tema | Editar o .config.mjs do tema + npm run tokens:themes + npm run tokens:build:all |
| Alterar foundation | Editar .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 output | Rodar npm run tokens:sync antes de tokens:build:all |
| Verificar sem gravar | theme-engine sync:architecture:test |
| Ver schema atual | theme-engine sync:architecture:schema |
Referências
- O que é o Theme Engine: 01-what-is-theme-engine.md
- Pipeline de build detalhado: 04-build-pipeline.md
- Formatos de output: 05-output-formats.md
- Quick start de engenharia: 09-engineering/01-quick-start.md
- Estrutura do workspace: 09-engineering/02-workspace-structure.md
- Referência completa de configuração: 09-engineering/03-theme-configuration.md
- Referência de CLI: 09-engineering/05-cli-reference.md
- Sistema de cores: 01-colors.md
- Camada Foundation: 05-foundation-layer.md