Design de UI

Criar Tokens de Cor Escaláveis para Design Systems

Pela Equipa do colorPaletteFinder9 min de leitura

O primeiro design system que construí foi lançado com exatamente um erro que assombrou tudo o que veio depois: usámos blue-500 diretamente no código dos componentes. Os botões eram blue-500. As ligações eram blue-500. O anel de foco era blue-500. Parecia limpo e DRY. Depois o marketing fez um rebranding de azul para turquesa, e gastámos dois sprints a perseguir cada referência hexadecimal literal por quarenta repositórios — e ainda assim falhámos algumas, porque algumas tinham sido fixadas a código como #3B82F6 em vez da variável. Todo esse episódio podia ter sido uma alteração de uma linha. A razão pela qual não foi é a razão inteira pela qual existem os tokens de cor de design system.

Um token de cor é apenas uma referência nomeada a um valor de cor. Mas o valor dos tokens não está na nomenclatura — está nas camadas de nomenclatura, e em ser disciplinado quanto à camada que um componente tem permissão para tocar. Acerte na estrutura em camadas e um rebranding, o lançamento de um modo escuro ou uma correção de contraste tornam-se uma alteração pequena e contida. Erre-a e fica com a limpeza de quarenta repositórios.

Três camadas: primitiva, semântica, de componente

O modelo mental que se aguentou em todos os sistemas em que trabalhei é uma hierarquia de três níveis. Cada nível referencia o que está abaixo dele, e os componentes só alguma vez têm permissão para consumir o nível de topo.

Eis a regra que faz a coisa toda funcionar, e a que a maioria das equipas salta: os componentes referenciam tokens semânticos ou de componente, nunca primitivos. No momento em que um botão estica o braço para lá da camada semântica para agarrar blue-500 diretamente, reintroduziu o problema dos quarenta repositórios um componente de cada vez.

Porquê ao certo dar-se ao trabalho da camada semântica intermédia? Porque é a costura onde acontece o tema. Quando muda para o modo escuro, não repinta cada componente — reaponta umas dezenas de tokens semânticos para primitivos diferentes. Os componentes não sabem que algo mudou. Essa indireção é a decisão de maior alavancagem de todo o sistema.

Construa a escala antes de nomear o que quer que seja

Antes da semântica, precisa de primitivos que valha a pena apontar — e isso significa uma escala numérica, convencionalmente de 50 a 950. A convenção popularizada pelo Tailwind (50, 100, 200 … 900, 950) tornou-se efetivamente o padrão da indústria, e vale a pena adotá-la só para que os novos engenheiros se sintam em casa. O 50 é a tinta mais clara, o 500 é aproximadamente o tom de marca puro, o 950 é quase preto.

O erro que os iniciantes cometem é gerar uma escala clareando e escurecendo ingenuamente em sRGB — somando branco para as tintas, preto para os tons. Obtêm-se meios-tons lamacentos e passos que não parecem uniformemente espaçados, porque a luminosidade percebida não é linear em RGB. Um 400 acaba por parecer quase idêntico a um 500, enquanto de 700 para 800 é um precipício. A solução é espaçar os seus passos num modelo percetual. As ferramentas que trabalham em HSL levam-no a meio caminho; as que usam OKLCH (agora bem suportado em CSS) levam-no o resto, porque o OKLCH mantém a luminosidade percebida consistente à medida que o tom muda. Se quiser avaliar à vista tintas e tons harmoniosos rapidamente enquanto prototipa uma escala, o gerador de paleta de cores é uma forma rápida de ver as relações entre tons antes de os comprometer a tokens.

Algumas regras conquistadas a custo para as escalas:

Nomenclatura: descreva a tarefa, não o aspeto

O princípio de nomenclatura mais importante é este: os tokens semânticos e de componente devem nomear o propósito, nunca a aparência. Um token chamado color-text-red parte no dia em que o texto de erro passa a laranja — o nome passa a mentir. Um token chamado color-feedback-error sobrevive a essa mudança intacto, porque "erro" continua a ser verdade independentemente do vermelho (ou laranja) para o qual resolve.

Uma estrutura de nomenclatura que escala lê-se da categoria geral para o modificador específico, porque ordena e audita de forma limpa:

Esse último — color-text-on-action — é um token que as pessoas esquecem e de que se arrependem. Quando a sua cor de ação é um azul saturado, o texto sobre esse azul precisa de ser branco ou quase-branco, e esse par nada tem a ver com color-text-primary. Faça dele um token explícito ou verá os engenheiros fixarem #FFFFFF nos botões e depois abrirem bugs de acessibilidade quando alguém introduzir uma ação de "aviso" amarelo-claro.

Para os primitivos, nomear a aparência é correto e esperado — blue-500 deve descrever o que é, porque é esse o objetivo inteiro da camada primitiva. A regra de "descrever a intenção" aplica-se acima dela.

Implementar com CSS custom properties

As CSS custom properties são o lar natural dos tokens porque cascateiam e podem ser sobrepostas pelo contexto — que é exatamente como funciona o tema. O padrão são duas camadas de variáveis: os primitivos definidos uma vez na raiz, os semânticos definidos na raiz e redefinidos sob um seletor de tema.

Defina os primitivos globalmente — --blue-600: #2563EB, --gray-900: #111827, e por aí adiante. Depois mapeie os semânticos para eles: --color-action-primary: var(--blue-600) e --color-text-primary: var(--gray-900). Os componentes só leem as variáveis semânticas: color: var(--color-text-primary). Nunca nomeiam um primitivo e nunca nomeiam um código hexadecimal.

No Tailwind v4, isto mapeia de forma limpa na nova diretiva @theme, que ao mesmo tempo declara os tokens e gera utilitários a partir deles, expondo cada token como uma variável CSS em tempo de execução. Define --color-text-primary: var(--color-gray-900) dentro de @theme, usa text-primary na marcação, e sobrepõe a variável sob um seletor .dark na sua base layer. A documentação oficial das theme variables do Tailwind cobre os mecanismos, e a mesma disciplina de duas camadas aplica-se independentemente da framework.

O modo escuro é um problema de mapeamento de tokens, não um problema de cor

A razão pela qual a camada semântica ganha o seu sustento é que o modo escuro se torna quase trivial assim que ela existe. Não toca num único componente. Sobrepõe os tokens semânticos dentro de um escopo .dark (ou [data-theme="dark"]) para que resolvam para primitivos diferentes.

Em modo claro, --color-bg-surface: var(--white) e --color-text-primary: var(--gray-900). Em modo escuro, sob o seletor .dark, --color-bg-surface: var(--gray-900) e --color-text-primary: var(--gray-100). Cada componente que lê color-bg-surface inverte automaticamente, porque é a cascata que faz o trabalho.

Duas coisas que mordem as pessoas aqui:

As armadilhas que partem mesmo à escala

Ao fim de rebrandings e lançamentos de tema suficientes, os modos de falha rimam:

O resumo honesto é que os tokens não tomam as decisões de cor por si — tornam as decisões de cor baratas de mudar. A estrutura é o que lhe compra a capacidade de fazer um rebranding numa sexta-feira, lançar o modo escuro num sprint, e corrigir um bug de contraste numa linha em vez de quarenta repositórios. Invista o seu esforço na camada semântica e na nomenclatura, mantenha os componentes honestos quanto ao nível que tocam, e o sistema absorverá mudanças que ainda nem consegue prever.

Perguntas Frequentes

Qual é a diferença entre tokens de cor primitivos e semânticos?

Um token primitivo (ou base) nomeia um valor de cor em bruto, sem contexto — por exemplo blue-500: #3B82F6. Descreve o que a cor é. Um token semântico nomeia o propósito da cor, como color-action-primary ou color-feedback-error, e aponta para um primitivo. Os componentes devem referenciar tokens semânticos, nunca primitivos, para que um rebranding ou uma mudança de tema exija apenas reapontar a camada semântica, em vez de editar todos os componentes.

Como devo nomear os tokens de cor num design system?

Nomeie os tokens semânticos e de componente pela intenção, não pela aparência. color-feedback-error sobrevive a um redesenho em que o vermelho de erro passa a laranja; color-text-red não. Use uma estrutura do geral para o específico, como categoria-propriedade-variante-estado (ex.: color-bg-surface-raised, color-action-primary-hover). Os tokens primitivos são a exceção — devem descrever a cor literal, como gray-900, porque é esse o seu propósito.

Porquê usar uma escala numérica de 50 a 950?

A escala de 50-950 (50, 100, 200 … 900, 950) popularizada pelo Tailwind tornou-se um padrão da indústria, por isso é instantaneamente familiar para novos engenheiros. O 50 é a tinta mais clara, o ~500 é o tom de marca puro, e o 950 é quase-preto. Espaçar os passos num modelo percetual como HSL ou OKLCH — em vez de misturar ingenuamente branco e preto em sRGB — mantém os passos com a sensação de estarem uniformemente espaçados e evita meios-tons lamacentos.

Como é que os tokens de cor facilitam o modo escuro?

O modo escuro torna-se um problema de mapeamento de tokens, e não um redesenho. Como os componentes só leem tokens semânticos, sobrepõe-se esses tokens dentro de um escopo .dark ou [data-theme=dark] para que resolvam para primitivos diferentes — por exemplo, color-bg-surface aponta para branco em modo claro e gray-900 em modo escuro. A cascata do CSS inverte cada componente automaticamente, sem alterações ao código dos componentes. Lembre-se de dessaturar as cores de destaque e de evitar fundos preto puro para reduzir o cansaço visual e a halação.

Que rácios de contraste é que os tokens de cor precisam de cumprir para a acessibilidade?

As WCAG exigem um rácio de contraste de pelo menos 4,5:1 para texto normal e 3:1 para texto grande e componentes de UI. Integre estas verificações no seu sistema de tokens validando cada par texto-sobre-fundo semântico em cada tema. Uma combinação que passa em modo claro pode falhar em modo escuro, por isso teste ambos, e defina um token explícito de texto-sobre-ação para garantir que as etiquetas em botões saturados permanecem legíveis.

Quer experimentar com cores?

Experimente o nosso gerador de paletas de cores gratuito para encontrar a harmonia perfeita — com um verificador de contraste WCAG integrado.

Abrir o Gerador