Por que implementar tokens com pré-processadores
Quando os tokens já existem como decisões de design (cores, espaçamentos, tipografia, raios, sombras), o desafio passa a ser operacional: como transformar esses valores em uma base consistente, fácil de consumir e difícil de quebrar. Pré-processadores como Sass/SCSS ajudam porque oferecem estruturas de dados (mapas), funções e loops para gerar CSS de forma padronizada. Isso permite: centralizar tokens em um único lugar, validar acessos (evitar “chutar” nomes), gerar utilitários e variações automaticamente e reduzir repetição.
Neste capítulo, o foco é implementar tokens com mapas e funções, e então usar esses tokens para gerar utilitários (classes prontas) e também para alimentar componentes. A ideia é que o time use tokens por meio de uma API (funções/mixins) em vez de acessar valores “na unha”.
Estrutura de tokens em mapas: modelando o que o time precisa
Em Sass, um mapa é uma estrutura chave-valor. Você pode representar tokens como mapas simples (um nível) ou mapas aninhados (vários níveis). A escolha depende de como você quer consultar e organizar os tokens.
Mapas simples (um nível)
Úteis quando a categoria é pequena e os nomes são diretos.
$colors: ( primary: #2f6fed, neutral-0: #ffffff, neutral-900: #111827, danger: #dc2626);Prós: simples de ler e consultar. Contras: pode ficar confuso quando cresce (ex.: variações de tema, estados, escalas).
Continue em nosso aplicativo
Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.
ou continue lendo abaixo...Baixar o aplicativo
Mapas aninhados (multi-nível)
Melhor para escalas e variações (ex.: spacing 1..10, typography sizes, temas).
$tokens: ( color: ( brand: ( primary: #2f6fed, secondary: #7c3aed ), neutral: ( 0: #ffffff, 100: #f3f4f6, 900: #111827 ), semantic: ( success: #16a34a, warning: #f59e0b, danger: #dc2626 ) ), space: ( 0: 0, 1: 0.25rem, 2: 0.5rem, 3: 0.75rem, 4: 1rem, 6: 1.5rem, 8: 2rem, 12: 3rem ), radius: ( sm: 0.25rem, md: 0.5rem, lg: 0.75rem, pill: 9999px ), shadow: ( sm: 0 1px 2px rgba(0,0,0,.08), md: 0 6px 16px rgba(0,0,0,.12) ));Esse formato facilita expandir sem “explodir” a lista de chaves. Também permite uma API de acesso consistente, como token(color, brand, primary) ou token(space, 4).
Passo a passo: criando uma API de acesso com funções
O objetivo é impedir que alguém use map-get diretamente em todo lugar, porque isso espalha a estrutura interna do mapa pelo projeto. Em vez disso, crie funções que encapsulam a consulta e fazem validação.
Passo 1: função para buscar chaves com validação
Vamos criar uma função genérica para navegar em mapas aninhados. Ela recebe o mapa base e uma lista de chaves (variádica). Se uma chave não existir, lança erro com mensagem clara.
@function map-deep-get($map, $keys...) { $value: $map; @each $key in $keys { @if type-of($value) != 'map' { @error 'map-deep-get: valor intermediário não é um mapa. Chaves: #{$keys}'; } @if map-has-key($value, $key) { $value: map-get($value, $key); } @else { @error 'map-deep-get: chave "#{$key}" não encontrada. Caminho: #{$keys}'; } } @return $value;}Com isso, você evita retornar null silenciosamente (o que costuma gerar CSS quebrado sem aviso).
Passo 2: função token() como API oficial
Agora, crie uma função que sempre consulta o mapa de tokens do projeto. Assim, se a estrutura mudar, você ajusta em um lugar só.
@function token($category, $keys...) { @return map-deep-get($tokens, $category, $keys...);}Uso:
.card { padding: token(space, 6); border-radius: token(radius, md); box-shadow: token(shadow, sm); background: token(color, neutral, 0); color: token(color, neutral, 900);}Passo 3: aliases e “atalhos” para categorias comuns
Se o time usa muito espaçamento e cores, você pode criar funções específicas para reduzir ruído e padronizar nomes.
@function space($step) { @return token(space, $step);} @function color($group, $name) { @return token(color, $group, $name);} @function radius($size) { @return token(radius, $size);}Uso:
.alert { padding: space(4); border-radius: radius(md); background: color(semantic, warning);}Modelando escalas e garantindo consistência
Tokens costumam ser escalas (ex.: spacing 0..12). Em pré-processadores, a escala pode ser um mapa explícito (mais controle) ou gerada por função (mais automático). Para sistemas reais, mapas explícitos são mais previsíveis: você decide exatamente quais passos existem e evita “passos fantasmas”.
Exemplo: escala de spacing com passos permitidos
Se alguém tentar usar space(5) e o passo 5 não existe, a função deve falhar. Isso força consistência e evita valores “quase iguais” espalhados.
$tokens: ( space: ( 0: 0, 1: 0.25rem, 2: 0.5rem, 3: 0.75rem, 4: 1rem, 6: 1.5rem, 8: 2rem ));Se o design decidir adicionar o passo 5, você adiciona no mapa e o sistema inteiro passa a suportar.
Geração de utilitários a partir de tokens
Uma das aplicações mais produtivas de tokens com pré-processadores é gerar utilitários (classes pequenas e previsíveis) automaticamente. Isso reduz trabalho manual e garante que utilitários usem apenas tokens aprovados.
O padrão geral é: definir um mapa de “propriedades utilitárias” e iterar sobre ele, combinando com escalas (space, cores, radius etc.).
Passo a passo: utilitários de espaçamento (margin/padding)
Passo 1: definir quais propriedades e abreviações serão geradas
Em vez de codar manualmente .m-1, .m-2, etc., definimos um mapa de utilitários e um mapa de direções.
$space-utils: ( m: margin, p: padding); $space-directions: ( '': '', t: '-top', r: '-right', b: '-bottom', l: '-left', x: ('-left', '-right'), y: ('-top', '-bottom'));Aqui, x e y são casos especiais (duas propriedades).
Passo 2: iterar sobre a escala de spacing e gerar classes
@mixin generate-space-utilities() { $scale: token(space); @each $abbr, $prop in $space-utils { @each $dir, $suffix in $space-directions { @each $step, $value in $scale { $class: if($dir == '', '.#{$abbr}-#{$step}', '.#{$abbr}#{$dir}-#{$step}'); #{$class} { @if type-of($suffix) == 'list' { #{$prop}#{nth($suffix, 1)}: $value; #{$prop}#{nth($suffix, 2)}: $value; } @else { #{$prop}#{$suffix}: $value; } } } } }}Uso:
// Em um arquivo de utilitários (ex.: _utilities.spacing.scss) @include generate-space-utilities();Resultado esperado (exemplos):
.m-4 { margin: 1rem; } .mt-2 { margin-top: 0.5rem; } .px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }Esse approach garante que só existam classes para passos definidos no token space.
Passo 3: adicionar variações negativas (opcional e controlado)
Nem todo projeto permite margens negativas. Se permitir, faça isso explicitamente para não virar “terra sem lei”.
@mixin generate-negative-margin-utilities($allowed: true) { @if $allowed != true { @return; } $scale: token(space); @each $step, $value in $scale { @if $step != 0 { .-m-#{$step} { margin: -$value; } .-mt-#{$step} { margin-top: -$value; } .-mx-#{$step} { margin-left: -$value; margin-right: -$value; } } }}Utilitários de cor a partir de tokens
Para cores, você pode gerar utilitários de color, background-color e border-color. O cuidado aqui é o tamanho do CSS: se você gerar tudo para todas as cores e todas as propriedades, pode ficar grande. Uma estratégia é gerar apenas grupos necessários (ex.: semantic + neutral) e deixar brand para componentes.
Passo a passo: gerar utilitários de texto e fundo
Passo 1: selecionar grupos de cores
$color-groups-for-utils: (neutral, semantic);Passo 2: mixin de geração
@mixin generate-color-utilities() { @each $group in $color-groups-for-utils { $palette: token(color, $group); @each $name, $value in $palette { .text-#{$group}-#{$name} { color: $value; } .bg-#{$group}-#{$name} { background-color: $value; } .border-#{$group}-#{$name} { border-color: $value; } } }}Exemplos gerados:
.text-semantic-danger { color: #dc2626; } .bg-neutral-100 { background-color: #f3f4f6; }Se o seu mapa de neutral usa chaves numéricas (0, 100, 900), isso funciona bem. Se usar strings, mantenha consistência para evitar classes “estranhas”.
Utilitários de radius e shadow
Raio e sombra são ótimos candidatos a utilitários porque são decisões visuais recorrentes. Aqui também vale o cuidado com tamanho: sombras podem ser muitas, então mantenha uma escala curta.
@mixin generate-radius-utilities() { $r: token(radius); @each $name, $value in $r { .radius-#{$name} { border-radius: $value; } }} @mixin generate-shadow-utilities() { $s: token(shadow); @each $name, $value in $s { .shadow-#{$name} { box-shadow: $value; } }}Uso:
@include generate-radius-utilities(); @include generate-shadow-utilities();Gerando utilitários responsivos com breakpoints em mapa
Um ganho importante de pré-processadores é gerar variações responsivas sem duplicar lógica. Para isso, defina breakpoints como tokens e crie um mixin que envolva regras em @media.
Passo 1: definir breakpoints como tokens
$tokens: map-merge($tokens, ( breakpoint: ( sm: 36rem, md: 48rem, lg: 64rem )));Passo 2: mixin de media query
@mixin up($bp) { $value: token(breakpoint, $bp); @media (min-width: $value) { @content; }}Passo 3: gerar utilitários com prefixo responsivo
Uma convenção comum é prefixar a classe com o breakpoint, por exemplo .md:mt-4. Em CSS puro, dois pontos precisam ser escapados. Em Sass, você pode gerar o seletor com escape \:.
$responsive-bps: (sm, md, lg); @mixin generate-responsive-space-utilities() { $scale: token(space); @each $bp in $responsive-bps { @include up($bp) { @each $step, $value in $scale { .#{$bp}\:mt-#{$step} { margin-top: $value; } .#{$bp}\:pt-#{$step} { padding-top: $value; } .#{$bp}\:px-#{$step} { padding-left: $value; padding-right: $value; } } } }}Você pode expandir isso para cobrir todas as direções e propriedades, mas faça com cuidado para não gerar CSS excessivo. Uma prática é gerar responsivo apenas para um subconjunto (ex.: spacing e display) e manter o restante não responsivo.
Organizando a geração: arquivos, flags e “build” previsível
Para manter controle, separe: (1) definição de tokens, (2) funções/mixins de acesso, (3) geradores de utilitários, (4) ponto de entrada que decide o que compilar. Assim, você consegue ligar/desligar famílias de utilitários e reduzir CSS quando necessário.
_tokens.scss: mapas de tokens
_token-functions.scss:
map-deep-get,token(),space(),color()_utilities.spacing.scss: mixins e includes de spacing
_utilities.color.scss: mixins e includes de cor
_utilities.effects.scss: radius/shadow
utilities.scss: arquivo agregador que inclui apenas o que o projeto quer
Exemplo de agregador com flags:
$enable-utilities-spacing: true !default; $enable-utilities-color: true !default; $enable-utilities-effects: false !default; @if $enable-utilities-spacing { @include generate-space-utilities(); @include generate-responsive-space-utilities(); } @if $enable-utilities-color { @include generate-color-utilities(); } @if $enable-utilities-effects { @include generate-radius-utilities(); @include generate-shadow-utilities(); }Isso ajuda a adaptar o “pacote” de utilitários por produto, sem mexer nos tokens.
Integração prática: consumindo tokens em componentes sem vazar detalhes
Mesmo com utilitários, componentes ainda precisam de tokens diretamente (ex.: um botão que usa brand primary e estados). A regra prática é: componente consome tokens via funções, não via valores literais e não via acesso direto ao mapa.
.button { display: inline-flex; align-items: center; gap: space(2); padding: space(3) space(4); border-radius: radius(md); background: color(brand, primary); color: token(color, neutral, 0); border: 1px solid transparent; } .button--danger { background: token(color, semantic, danger); } .button--ghost { background: transparent; border-color: token(color, neutral, 900); color: token(color, neutral, 900); }Se amanhã a cor brand.primary mudar, você altera no token e recompila. Se o time decidir renomear grupos, você ajusta a função color() ou o mapa, mantendo o consumo consistente.
Validações e “guard rails”: evitando tokens inválidos e CSS silenciosamente errado
O maior benefício da função com @error é falhar cedo. Alguns cuidados adicionais:
Padronize chaves: evite misturar números e strings sem necessidade. Se usar números (0, 100, 900), use isso consistentemente no mapa e no consumo.
Evite tokens “soltos”: se um valor não é para ser usado, não coloque no mapa. Isso reduz escolhas e melhora consistência.
Crie funções por domínio:
space(),radius(),z()(z-index),duration()(transições). Isso torna o código autoexplicativo.Erros com mensagens úteis: inclua o caminho de chaves no erro para facilitar correção.
Estratégias para controlar tamanho do CSS gerado
Geração automática pode inflar o CSS. Para manter sob controle:
Gere apenas o necessário: selecione grupos de cores para utilitários; mantenha brand para componentes.
Limite escalas: spacing com poucos passos bem escolhidos costuma ser suficiente.
Evite combinações explosivas: responsivo para tudo + todas as direções + todas as escalas pode multiplicar classes rapidamente.
Use flags: habilite famílias de utilitários por projeto/página quando fizer sentido.
Exemplo completo: tokens + funções + geração mínima de utilitários
A seguir, um recorte que mostra uma implementação enxuta, mas realista, para você adaptar.
// _tokens.scss $tokens: ( color: ( neutral: (0: #fff, 100: #f3f4f6, 900: #111827), semantic: (danger: #dc2626, success: #16a34a) ), space: (0: 0, 1: .25rem, 2: .5rem, 3: .75rem, 4: 1rem, 6: 1.5rem), radius: (sm: .25rem, md: .5rem) ); // _token-functions.scss @function map-deep-get($map, $keys...) { $value: $map; @each $key in $keys { @if type-of($value) != 'map' { @error 'map-deep-get: caminho inválido: #{$keys}'; } @if map-has-key($value, $key) { $value: map-get($value, $key); } @else { @error 'map-deep-get: chave ausente: #{$key} em #{$keys}'; } } @return $value; } @function token($category, $keys...) { @return map-deep-get($tokens, $category, $keys...); } @function space($step) { @return token(space, $step); } @function radius($name) { @return token(radius, $name); } // _utilities.spacing.scss @mixin generate-space-utilities() { $scale: token(space); @each $step, $value in $scale { .p-#{$step} { padding: $value; } .pt-#{$step} { padding-top: $value; } .px-#{$step} { padding-left: $value; padding-right: $value; } .m-#{$step} { margin: $value; } .mt-#{$step} { margin-top: $value; } } } // utilities.scss @include generate-space-utilities();Mesmo esse conjunto mínimo já cobre grande parte do dia a dia com consistência total com os tokens.