Executando verificação de segurança...
1

bdsg: Gerando Design Tokens Programaticamente com Acessibilidade WCAG

Sabe aquela sensação quando você está criando um design system e percebe que está fazendo as mesmas contas repetidamente? Calcular contraste de cores, gerar tons de uma paleta, criar escalas tipográficas harmoniosas... É trabalho manual, repetitivo e cheio de oportunidades para errar. Foi exatamente esse problema que me levou a criar essa lib bdsg.

A ideia por trás da lib

A premissa é simples: design tokens deveriam ser gerados programaticamente, não manualmente. Quando você tem uma cor base da marca, por exemplo #3B82F6, você não deveria precisar abrir o Figma ou uma calculadora de cores para gerar os 10 tons da paleta. Mais importante ainda: você não deveria precisar verificar manualmente se cada tom tem contraste suficiente com preto ou branco para passar nos critérios WCAG.

A bdsg nasceu dessa necessidade. É uma biblioteca feita em TypeScript que pega conceitos de design (cores, tipografia, espaçamento, sombras) e os transforma em tokens validados e acessíveis. Tudo com validação Zod, então você descobre erros em tempo de desenvolvimento, não em produção.

Como funciona na prática

Vamos começar com o caso mais comum: você tem uma cor da marca e precisa gerar uma paleta completa. A lib faz isso preservando o matiz (a identidade visual da marca) enquanto ajusta luminosidade e saturação para criar tons harmoniosos. Mas o diferencial está no detalhe: para cada tom gerado, ela calcula automaticamente qual cor de texto (preto ou branco) oferece melhor contraste.

import { generatePalette } from 'bdsg';

const palette = generatePalette('#3B82F6', 'primary');

O resultado é uma paleta de 10 tons (50 a 900) onde cada um vem com três informações: o valor da cor, a cor de texto ideal, e a razão de contraste. Isso significa que você pode usar qualquer tom da paleta com confiança, sabendo que a acessibilidade já foi calculada.

Mas e quando a cor que você quer usar simplesmente não passa no WCAG? É aí que entra uma das partes mais interessantes da biblioteca: o ajuste automático de cores. A ideia é preservar ao máximo a identidade visual enquanto ajusta o mínimo necessário para atingir o contraste exigido.

import { adjustColorForContrast } from 'bdsg';

const result = adjustColorForContrast('#87CEEB', '#FFFFFF', 'AA', 'normal');

O algoritmo funciona em três etapas. Primeiro, ele tenta ajustar apenas a luminosidade usando busca binária - isso é rápido e preserva completamente o matiz e a saturação. Se isso não for suficiente, ele começa a reduzir a saturação gradualmente. Em último caso, quando nenhuma variação da cor consegue atingir o contraste necessário, ele faz fallback para preto ou branco. O resultado vem com metadados: quantas iterações foram necessárias, qual estratégia foi usada, e qual a razão de contraste final.

Escalas e progressões matemáticas

Tipografia é outro domínio onde a matemática pode ajudar muito. A biblioteca implementa escalas tipográficas baseadas em ratios musicais - a mesma matemática que torna música harmoniosa pode tornar sua tipografia harmoniosa. Você escolhe um ratio (quarta perfeita, proporção áurea, etc.) e a biblioteca gera uma escala completa.

import { generateTypographyScale } from 'bdsg';

const scale = generateTypographyScale({
  base: 16,
  ratio: 'perfect-fourth',
  stepsUp: 5,
  stepsDown: 2
});

O interessante é que a biblioteca não apenas calcula os tamanhos de fonte. Ela também ajusta automaticamente line-height (mais apertado em títulos grandes, mais solto em texto pequeno), letter-spacing (negativo em display, positivo em texto muito pequeno), e até sugere font-weights apropriados para cada tamanho. São detalhes tipográficos que designers experientes aplicam intuitivamente, mas que podem ser calculados matematicamente.

Para espaçamento, a abordagem é similar mas com mais opções. Você pode usar progressão de Fibonacci para um feeling mais orgânico, linear para um grid consistente, exponencial para hierarquia dramática, ou até nomes semânticos tipo t-shirt (xs, sm, md, lg, xl). Cada método tem seu caso de uso: Fibonacci funciona bem para espaçamentos naturais, linear é ótimo para grids, exponencial cria hierarquia visual forte.

Sombras e profundidade

Sombras são surpreendentemente complexas quando você quer fazer direito. O Material Design, por exemplo, usa três camadas de sombra para cada elevação: umbra (a sombra principal), penumbra (a transição), e ambiente (a sombra difusa). A biblioteca implementa isso e mais alguns estilos.

import { generateShadows, SHADOW_PRESETS } from 'bdsg';

const shadows = generateShadows(SHADOW_PRESETS.material);

Cada preset tem características diferentes. Material é realista com três camadas, Soft tem blur alto para um visual moderno e difuso, Hard tem blur mínimo para um look retrô, Brutalist é literalmente sem blur nenhum, e Neumorphism combina sombras internas e externas para aquele efeito de relevo.

Validação e type safety

Uma decisão importante foi usar Zod para validação. Isso significa que toda entrada é validada antes de ser processada. Se você tentar gerar uma escala tipográfica com base menor que 8px, a biblioteca vai reclamar. Se você passar uma cor em formato inválido, vai reclamar. Se você tentar criar uma escala de espaçamento com 50 steps (o que seria impraticável), vai reclamar.

generateTypographyScale({ base: 4 });
// Error: Invalid typography config: Number must be >= 8

Isso pode parecer chato, mas na prática significa que você descobre problemas imediatamente, não quando um usuário reporta que o site está quebrado. E como é TypeScript, seu editor já te avisa dos problemas antes mesmo de rodar o código.

Performance e otimizações

Cálculo de luminância é uma operação relativamente cara (envolve conversão de espaço de cor e exponenciação). A biblioteca implementa um cache de luminância, então se você calcular contraste entre as mesmas cores múltiplas vezes, apenas a primeira vez faz o cálculo pesado. As chaves do cache são normalizadas (uppercase, sem espaços), então #3b82f6 e #3B82F6 compartilham o mesmo cache.

O ajuste de cores usa busca binária ao invés de busca linear. Isso significa que ao invés de testar 100 valores de luminosidade um por um, a biblioteca converge para o valor ideal em cerca de 8 iterações. É a diferença entre O(n) e O(log n).

Casos de uso reais

Um caso de uso comum é gerar um design system completo programaticamente. Você define as cores base da marca, escolhe ratios para tipografia e espaçamento, seleciona um estilo de sombra, e a biblioteca gera todos os tokens. Cada módulo pode exportar CSS variables, então você pode literalmente copiar e colar o output direto no seu CSS.

Outro caso interessante é validação de contraste em CI. Você pode escrever testes que verificam se todas as combinações de cores do seu design system passam no WCAG AA. Se um designer mudar uma cor e quebrar a acessibilidade, o CI vai pegar antes de ir para produção.

import { calculateContrast, meetsWCAG } from 'bdsg';

const colors = [
  ['#3B82F6', '#FFFFFF'],
  ['#10B981', '#FFFFFF'],
];

for (const [fg, bg] of colors) {
  const ratio = calculateContrast(fg, bg);
  if (!meetsWCAG(ratio, 'AA', 'normal')) {
    throw new Error(`${fg} on ${bg} fails WCAG AA`);
  }
}

Tem também o caso de gerar temas automáticos. Você passa uma cor da marca e a biblioteca gera variações claras e escuras que são garantidamente acessíveis. Isso é útil para criar temas claro/escuro ou variações de uma mesma cor para diferentes contextos.

Detecção de relações entre tokens

Uma funcionalidade mais experimental é a detecção de relações entre tokens. A ideia é que se você tem um token chamado primary e cria outro chamado primary-text, provavelmente existe uma relação semântica entre eles. A biblioteca usa heurísticas baseadas em padrões de nomenclatura para detectar essas relações automaticamente.

import { detectRelations } from 'bdsg';

const newToken = { id: '3', name: 'primary-text', category: 'color' };
const existing = [{ id: '1', name: 'primary', category: 'color' }];

detectRelations(newToken, existing);

Ela detecta padrões como sufixos (-text, -bg, -hover), hierarquia (-base, -default), e shades de paleta (-500, -600). Cada relação vem com um score de confiança. É útil para construir grafos de dependência entre tokens ou para sugerir relações em ferramentas de design.

Testes e confiabilidade

A biblioteca tem 248 testes cobrindo desde conversões básicas de cor até edge cases específicos. Por exemplo, tem testes que verificam se a razão de contraste entre preto e branco é exatamente 21:1, se os coeficientes de luminância RGB estão corretos (R: 0.2126, G: 0.7152, B: 0.0722), e se thresholds WCAG são respeitados com precisão (4.499 falha, 4.5 passa).

Tem também testes para casos extremos como cinza em cinza (pior caso possível para contraste), conversões round-trip (HEX > RGB > HSL > HEX deve retornar a cor original), e validação de entrada (hex de 4 caracteres deve falhar, shorthand de 3 deve funcionar).

Como começar

A instalação é padrão:

npm install bdsg

O repositório tem 6 exemplos práticos que você pode rodar localmente. Cada exemplo demonstra um módulo diferente da biblioteca: geração de paleta, escala tipográfica, espaçamento, sombras, validação WCAG, e um exemplo completo gerando um design system inteiro.

O que vem por aí

Tenho algumas ideias para próximas versões. Geração de gradientes acessíveis seria útil - garantir que um gradiente mantém contraste suficiente em todos os pontos. Temas claro/escuro automáticos também está na lista - você passa um design system e a biblioteca gera a versão dark automaticamente. Exportação para Figma Tokens seria interessante para integrar com ferramentas de design. E talvez um CLI para gerar tokens direto do terminal sem precisar escrever código.

Conclusão

A bdsg é uma tentativa de trazer mais automação e confiabilidade para a criação de design tokens. Se você trabalha com design systems, especialmente se precisa garantir acessibilidade WCAG, pode ser útil. A biblioteca está no npm como bdsg, o código está no GitHub, e tem documentação completa com exemplos.

Links:

E aí, o que achou? Já teve que fazer algo parecido? Comenta aí!

Carregando publicação patrocinada...