DEV Community

Guilherme Marucchi
Guilherme Marucchi

Posted on

Duas identidades visuais no mesmo sistema, e a troca que custou quatro linhas

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)