Capa do Ebook gratuito Arquitetura de CSS Escalável: BEM, ITCSS e Design Tokens para Projetos Reais

Arquitetura de CSS Escalável: BEM, ITCSS e Design Tokens para Projetos Reais

Novo curso

22 páginas

Implementação de tokens com pré-processadores: mapas, funções e geração de utilitários

Capítulo 11

Tempo estimado de leitura: 11 minutos

+ Exercício

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...
Download App

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.

Agora responda o exercício sobre o conteúdo:

Qual é a principal vantagem de usar uma função token() como API oficial para acessar valores de design tokens em vez de usar map-get diretamente em vários pontos do projeto?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Uma API como token() centraliza o acesso aos tokens, evita espalhar map-get pelo projeto e adiciona validação com @error, prevenindo CSS quebrado por chaves inválidas e facilitando mudanças na estrutura dos mapas.

Próximo capitúlo

Camada de estilos base e elementos: tipografia, links, listas e normalização controlada

Arrow Right Icon
Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.