No meio de uma migração de design system, a marca mudou.
Não era o plano. O plano era trocar a base técnica dos componentes. O antigo saindo tela a tela, o novo
entrando no lugar. Aí veio a informação de que a identidade visual também ia evoluir: cor primária diferente,
neutros diferentes, tipografia diferente.
Duas mudanças grandes ao mesmo tempo, num sistema com mais de 450 arquivos de estilo e mais de cem telas, sem
poder parar a entrega de produto.
Isto é o que fez a segunda mudança custar quatro linhas em vez de uma varredura.
O problema, sem a camada
Num sistema comum, o componente sabe a cor:
o botão primário é laranja
o card tem fundo branco
a borda é cinza claro
Espalhe isso por algumas centenas de componentes e a troca de identidade vira uma varredura: abrir arquivo
por arquivo, achar cada cor, trocar. E sempre escapa alguma. A que fica é a que ninguém vai encontrar até
um usuário reclamar.
O sistema já tinha um passo dado nessa direção: as cores estavam em variáveis CSS, não escritas à mão em cada
lugar. Isso ajuda, mas não resolve. Porque a variável dizia qual cor é, não para que ela serve:
--marca-laranja-puro: #f60;
E o componente consumia isso direto. Se a marca deixasse de ser laranja, o nome mentia. E se você quisesse
laranja em dois papéis diferentes: ação principal e destaque de aviso? Não havia como mudar um sem mudar
o outro.
Duas camadas, e a distinção é tudo
A separação que resolve é entre paleta e papel.
Paleta: o que a cor é. Um dicionário de tintas. Não diz para que serve.
--paleta-marca: #f60;
--paleta-cinza-200: #e5e5e5;
Papel: o que a cor significa. Um dicionário de funções, apontando para o de tintas.
--primary: var(--paleta-marca);
--border: var(--paleta-cinza-200);
Os componentes consomem só a segunda camada. Nenhum componente sabe qual cor ele está usando; ele sabe
que está pintando uma ação principal.
Parece burocracia até você precisar trocar a identidade. Aí a troca acontece no lado direito de uma linha, e
todos os componentes acompanham. Inclusive os que ainda não foram escritos.
O truque: construir a camada antes de as cores existirem
Aqui está a parte que eu não teria pensado se não fosse a pressão de não ficar parado.
Quando montamos a estrutura, o time de design ainda não tinha entregado a identidade nova. A tentação era
esperar. Em vez disso, declaramos a camada de papéis inteira apontando para as cores antigas:
--primary: var(--paleta-antiga-marca);
--background: var(--paleta-antiga-fundo);
Provisório de propósito. E não é rodeio. Tem duas razões concretas.
Primeira: alguma coisa precisa estar lá. Variável sem valor não renderiza nada, e o catálogo de
componentes que adotamos vem com uma paleta padrão própria. Sem sobrescrever, o sistema inteiro renderizaria
na cara genérica da biblioteca. Nem a identidade antiga, nem a nova.
Segunda, e é a que importa: com as cores antigas, dá para isolar a variável. Montamos uma bancada com um
componente antigo e um novo lado a lado. Se os dois aparecem na mesma cor, a corrente inteira funciona:
token, mapeamento, geração de CSS, pixel na tela. Se aparecem diferentes, alguma coisa quebrou no meio. Com
cores diferentes nos dois lados, não dá para saber se a diferença é bug ou é só a cor.
As cores antigas viraram sinal de teste, não escolha estética.
Quando o design entregou a identidade nova, a implementação foi trocar o lado direito das declarações. A
estrutura já estava provada.
Os dois cenários que a camada torna possíveis
Com paleta e papel separados, existem duas trocas diferentes e a diferença entre elas é o que permite
migrar sem quebrar nada.
Trocar a paleta muda os dois mundos juntos. O componente antigo lê a paleta direto; o novo lê o papel, que
aponta para a paleta. Mudou a tinta, mudou tudo.
Trocar o papel muda só o mundo novo. O componente antigo continua lendo a paleta antiga, intocado.
O segundo é o que a coexistência precisa: a identidade nova entra nos componentes novos enquanto os antigos
seguem como estão. As duas gerações convivem, cada uma lendo a sua camada.
Onde o modelo cedeu
Isso funcionou enquanto a migração só acrescentava. Tela nova nascia lendo o papel, tela antiga seguia na
paleta, e as duas não se cruzavam.
Parou de valer no dia em que telas que já existiam tiveram que vestir a pele nova. Consolidamos cinco telas de
área financeira numa página com abas, depois quatro de outra área. Não eram componentes novos: eram os mesmos
de sempre, precisando de fundo e borda diferentes.
Reescrever cada um na estrutura nova custaria semanas e não era o que estava sendo pedido. Então fizemos o que
o modelo mandava não fazer: nove arquivos de estilo antigos passaram a ler a camada de papel.
/* dentro de um componente antigo */
body.menu-v2 & {
background: var(--card);
border: 1px solid var(--sidebar-border);
}
Com isso, "trocar o papel muda só o mundo novo" deixou de ser verdade. Mexer em --card hoje afeta os dois
lados.
O que segurou a coexistência foi o portão mudar de natureza. Antes ele era implícito, dado pela
arquitetura: você estava protegido porque lia outra camada. Agora é explícito, numa classe no body que só
existe quando a flag do menu novo está ligada. Com a flag desligada, aquelas nove regras não casam com nada e
o componente antigo renderiza exatamente como antes.
Trocamos garantia estrutural por garantia condicional. É pior de auditar e melhor de entregar. E essa é a
troca honesta: o modelo puro exigiria reescrever o componente antes de poder mudar a aparência dele, e isso
não caberia no cronograma.
Um detalhe de CSS que me pegou
Montei uma demonstração com botões para alternar entre os cenários e mostrar isso ao vivo. Aplicei as
variáveis numa <div> que envolvia os componentes.
Funcionou para um lado e não para o outro. Trocar o papel mudava o componente novo; trocar a paleta não
mudava nada no novo.
O motivo é uma sutileza que vale conhecer: a substituição do var() acontece no elemento onde a
propriedade é declarada, não onde ela é usada.
O papel estava declarado no :root, apontando para a paleta:
:root {
--paleta-marca: #f60;
--primary: var(--paleta-marca);
}
Nesse momento, ali no :root, o --primary já foi resolvido para #f60. O que desce por herança para os
elementos filhos é a cor pronta, não a fórmula. Sobrescrever --paleta-marca numa <div> lá embaixo não
tinha como voltar atrás e recalcular.
Em produção isso não acontece, porque as duas camadas são declaradas no mesmo :root. Trocar a paleta ali
propaga de verdade. O erro era só da bancada, e a correção foi aplicar as variáveis no próprio :root, que
é o que espelha o comportamento real.
O nome do token é uma decisão de longo prazo
Uma armadilha que aparece exatamente nesse momento: a tentação de marcar a geração no nome.
--primary-v2
--marca-nova
--cor-primaria-2026
Parece organizado. É uma bomba-relógio: em dezoito meses a "v2" é a única que existe, e o nome mente para
todo mundo que ler. Aí alguém cria a v3.
A regra que sobrevive: nome de tinta descreve a cor; nome de papel descreve a função. Nenhum dos dois
carrega versão.
Onde a versão aparece é no portão, e ali ela é bem-vinda: existe uma classe menu-v2 no body, com o número
no nome de propósito. A diferença é o tempo de vida. Token é vocabulário permanente, e quando o nome mente
a dívida fica para sempre. A classe é temporária por construção: quando a migração terminar, ela sai e
leva o número junto.
O que não deu certo
Duas coisas, para não parecer que foi tranquilo.
A tipografia não cabe no mesmo modelo. Cor é fácil de escopar: o componente novo lê o papel novo, o antigo
lê a paleta antiga, e eles não se falam. Fonte é herdada. O texto que não declara família herda do body, e
trocar o padrão do body mudaria a fonte de todo o texto herdado de uma vez. Sem lista, sem teste que
pegue.
A saída foi escopar a fonte nova aos componentes novos por um atributo que só eles têm, e deixar a inversão
para o fim da migração, quando o antigo não existir mais. Funciona, mas é remendo com data marcada, não
elegância.
O contraste ficou abaixo do mínimo. Medi os pares de cor da identidade nova contra o critério de
acessibilidade: nenhum atinge o mínimo para texto pequeno, que é o tamanho do rótulo padrão dos botões. Levei
ao design com os números; a decisão foi manter.
Registro os dois porque é o que separa relato de propaganda. A camada de tokens resolveu o problema para o
qual foi feita. Não resolveu tudo.
Segundo de três textos sobre migrar um design system em produção sem parar a entrega. O primeiro é sobre um
botão que quase desapareceu ao instalar uma biblioteca de CSS. O próximo é sobre por que quase toda regra de
migração que vive num documento acaba sendo violada, e o que fazer a respeito.
Top comments (0)