Design tokens: o que são e como contribuem para a acessibilidade

Design tokens: o que são e como contribuem para a acessibilidade
Lorena Garcia
Lorena Garcia

Compartilhe

Se você já tentou criar um projeto acessível e percebeu como pode ser desafiador manter cores, espaçamentos e tipografia sempre alinhados, este artigo é para você.

Design tokens ajudam a padronizar estilos e manter a harmonia visual do projeto. Com eles, é possível armazenar valores reutilizáveis, como cores, espaçamentos e tamanhos de fontes, criando um sistema visual bem estruturado e mais fácil de adaptar caso necessário.

Além disso, design tokens também facilitam a escalabilidade do projeto mantendo a consistência entre plataformas e melhoram a manutenção do código a longo prazo.

Outro ponto importante é que eles também contribuem para a acessibilidade, garantindo uma experiência mais inclusiva.

No desenvolvimento para web, a acessibilidade depende de boas práticas no design: um contraste bem ajustado, tamanhos de texto apropriados e espaçamentos bem distribuídos fazem toda a diferença para a experiência da pessoa usuária.

Agora, você deve estar se perguntando: "Ok, mas como colocar isso em prática?"

O Styled Components é um grande amigo nesse processo, ele faz com que possamos organizar design tokens de forma prática, centralizando estilos e adaptando temas conforme a necessidade do nosso projeto.

Neste artigo, vamos explorar:

  • O que são design tokens e porque são úteis;
  • Como eles contribuem para acessibilidade;
  • Configuração de um sistema de temas, incluindo suporte a modo escuro;
  • E mais!

Tudo isso com exemplos práticos para você testar no seu projeto. Então, aperte o cinto e vamos nessa!

O que são design tokens?

Design tokens são valores reutilizáveis que definem cores, tamanhos de fonte, espaçamentos, bordas e sombras em um projeto.

Em vez de espalhar esses valores pelo código, eles ficam guardados em um único lugar.

Mas aí você pode se perguntar “Por que usar isso? Não vai dar mais trabalho?”.

Vamos lá, imagina que precisamos mudar a cor principal dos botões do nosso projeto e ele já está bem grande.

Se essa cor estiver escrita diretamente em vários arquivos (exemplo: #007AFF), vamos precisar buscar e alterar em cada lugar em que for necessário, olha o trabalhão!

E se depois precisar mudar o tamanho de uma fonte? Lá vamos nós de novo procurar e alterar isso em muitos lugares, isso pode deixar o código confuso e trabalhoso de atualizar.

Com design tokens, cores, tamanhos de fonte, dentre outros, são definidas em um único lugar, e todas as partes do código que fazem referência a elas são atualizadas automaticamente.

Assim conseguimos evitar que, por algum engano, esqueçamos de mudar em um lugar do projeto.

Nesse artigo iremos utilizar um projeto simples para exemplificar o uso de design tokens e styled components.

Se você quiser conferir o projeto completinho com passo a passo para rodar na sua máquina, pode ver no repositório no GitHub.

Vamos ver um exemplo de organização de tokens?

Por questão de organização e boas práticas, iremos separar por arquivos. Então vamos criar uma pasta theme, dentro do src e nela vamos adicionar os arquivos dividindo-os por responsabilidades.

O arquivo colors.js, onde vamos colocar todas as cores do projeto organizadas por tokens. Ou seja, em vez de usar valores fixos, teremos nomes que representam essas cores.

Assim, sempre que precisarmos de uma cor, basta chamar o token correspondente, por exemplo: blue100, blue200e assim por diante, como mostro abaixo:

export const colors = {
    blue100: "#005BBB",  
    blue200: "#007AFF",  
    gray100: "#F0F0F0",  
    gray200: "#B3B3B3",  
    black: "#1A1A1A",    
    white: "#FFFFFF",  
    yellow100: "#E69500", 
    yellow200: "#FFC300", 
    transparent: "transparent"
};

O arquivo breakpoints.js, onde definiremos os pontos de quebra, para que o site seja responsivo.

export const breakpoints = {
    small: "600px",
    medium: "960px",
    large: "1280px",
    extraLarge: "1920px"
};

export const media = {
    small: `@media (min-width: ${breakpoints.small})`,
    medium: `@media (min-width: ${breakpoints.medium})`,
    large: `@media (min-width: ${breakpoints.large})`,
    extraLarge: `@media (min-width: ${breakpoints.extraLarge})`
};

E theme.jsonde teremos a estrutura central de temas, que inclui a importação dos arquivos colors.js, breakpoints.js e a definição dos dois temas que teremos: claro e escuro.

import { colors } from "./colors";
import { breakpoints, media } from "./breakpoints";

export const lightTheme = {
    breakpoints,
    media,
    typography: {
        fontSize: {
            title: "clamp(1.25rem, 1.5vw, 1.5rem)",
            subtitle: "clamp(1rem, 1.2vw, 1.125rem)",
            body: "clamp(0.875rem, 1vw, 1rem)"
        },
        fontFamily: {
            primary: "sans-serif",
            secondary: "serif"
        }
    },
    colors: {
        background: {
            primary: colors.gray100,
            secondary: colors.white
        },
        text: {
            title: colors.black,
            body: colors.black,
            placeholder: colors.gray200,
            button: colors.white
        },
        primary: colors.blue100,
        secondary: colors.blue200,
        focus: colors.yellow100,
        disabled: colors.gray100,
        transparent: colors.transparent
    },
    padding: {
        small: "0.5rem",
        medium: "1rem",
        large: "1.5rem"
    },
    margin: {
        small: "0.5rem",
        medium: "1rem",
        large: "1.5rem"
    },
    gap: {
        small: "0.5rem",
        medium: "1rem",
        large: "1.5rem"
    },
    borderWidth: {
        thin: "1px",
        thick: "2px"
    },
    borderRadius: {
        small: "4px",
        medium: "8px"
    },
    boxShadow: {
        soft: `0px 2px 4px ${colors.black}1A`,
        medium: `0px 4px 8px ${colors.black}26`,
        strong: `0px 6px 12px ${colors.black}33`
    }
};

export const darkTheme = {
    ...lightTheme,
    colors: {
        background: {
            primary: colors.black,
            secondary: colors.black
        },
        text: {
            title: colors.white,
            body: colors.white,
            placeholder: colors.gray100,
            button: colors.white
        },
        primary: colors.blue200,
        secondary: colors.blue100,
        focus: colors.yellow200,
        disabled: colors.gray200,
        transparent: colors.transparent
    }
};

Eita, muita coisa né? Mas não se preocupa, isso pode parecer muito código e mais trabalho no começo, mas, no final, vai te poupar um tempão.

Porque se você precisar alterar qualquer valor, como uma cor ou um tamanho de fonte, é só mudar uma vez, e o efeito será aplicado em todo o projeto.

Assim a facilidade para manter só aumenta e quando outra pessoa bater o olho no código da pasta theme vai entender do que se trata e saberá que ali que precisa modificar, por exemplo a cor primária.

Banner da Alura apresentando a Imersão Mobile, uma oportunidade para aprender Flutter criando um app de delivery na prática. Participe de 3 aulas gratuitas, desenvolva um projeto para portfólio e conquiste seu certificado!

Acessibilidade e design tokens

Se tem uma coisa que pode transformar um design bonito em uma experiência realmente agradável e funcional, é a acessibilidade. E adivinha? Design tokens podem te ajudar bastante nisso!

Vamos começar pelo contraste, que é um dos pilares para garantir que todo mundo consiga enxergar os elementos da página sem dificuldade.

Se o texto e o fundo tiverem cores muito próximas, fica praticamente impossível de ler. E é aqui que entra o WCAG (Web Content Accessibility Guidelines), um conjunto de diretrizes que define níveis mínimos de contraste para garantir que a informação seja visível para a maioria das pessoas.

Ele serve como referência para pessoas designers e desenvolvedoras ao criar interfaces mais inclusivas.

As diretrizes do WCAG são divididas em três níveis: A, AA e AAA. O nível A cobre os requisitos mais básicos, enquanto o nível AA é o mais recomendado, pois equilibra acessibilidade e design.

Já o nível AAA é mais rigoroso e pode ser difícil de aplicar em todos os projetos.

Um dos aspectos mais conhecidos do WCAG é o contraste de cores, o nível AA exige um contraste mínimo de 4.5:1 para textos normais e 3:1 para textos maiores. Isso garante que a leitura fique mais confortável, especialmente para pessoas com daltonismo ou baixa visão.

E, as diretrizes incluem recomendações sobre o uso de textos alternativos para imagens (ALTs), navegação acessível pelo teclado e a possibilidade de ajustar tamanhos de fonte sem comprometer a usabilidade.

Para quem desenvolve interfaces, o WCAG pode servir como um guia prático para evitar barreiras desde o início do projeto.

Se você tiver curiosidade, existem alguns sites que ajudam a testar a acessibilidade, uma delas é o Contrast Checker.

Também é possível usar o polished.js, que oferece funções super bacanas como getContrast() e getLuminance(). A função getContrast() retorna a taxa de contraste entre duas cores, ajudando a verificar se elas atendem os padrões recomendados pelo W3C, a organização que desenvolve as diretrizes do WCAG.

getLuminance() retorna um número representando a luminância de uma cor, esse valor pode ser usado para comparar cores e escolher automaticamente aquela com melhor visibilidade. Legal, né?

Se você quiser ler mais sobre, instalação, uso etc., recomendo a leitura da documentação que é bem completa, com muitos exemplos.

Agora, bora falar de tamanhos de fonte! Sabe quando você acessa um site no celular e o texto parece minúsculo?

Homem com expressão séria segurando uma lupa próxima ao rosto enquanto olha para a tela do celular com atenção. Ele move a lupa para tentar enxergar melhor, sugerindo que a tela tem algo pequeno ou difícil de ler.

Isso acontece quando os tamanhos das fontes não são escaláveis. Para evitar esse problema, podemos usar unidades relativas como rem e em, e a função clamp(), que permite definir um intervalo de tamanho mínimo e máximo para que a fonte se adapte melhor aos diferentes tamanhos de tela.

No nosso tema, já deixamos isso pronto:

fontSize: {
    title: "clamp(1.25rem, 1.5vw, 1.5rem)",
    subtitle: "clamp(1rem, 1.2vw, 1.125rem)",
    body: "clamp(0.875rem, 1vw, 1rem)"
},

Assim, garantimos que os títulos, subtítulos e textos tenham tamanhos responsivos sem precisar ajustar manualmente para cada dispositivo.

E o espaçamento? Pois é, uma página sem espaçamentos bem definidos pode virar um verdadeiro pesadelo visual. Botões colados, textos apertados... ninguém merece!

Para resolver isso, usamos tokens de espaçamento bem arrumadinhos. Definimos margens, paddings e gaps dentro do nosso tema para que o layout fique sempre bonitinho e confortável para leitura.

padding: {
    small: "0.5rem",
    medium: "1rem",
    large: "1.5rem"
},
margin: {
    small: "0.5rem",
    medium: "1rem",
    large: "1.5rem"
},
gap: {
    small: "0.5rem",
    medium: "1rem",
    large: "1.5rem"
},

Fazendo desse jeito, evitamos layouts inconsistentes e melhora bastante a experiência da pessoa usuária.

E então temos bordas e sombras, que não são apenas detalhes estáticos, mas também elementos que ajudam na acessibilidade.

Bordas bem definidas garantem que os componentes tenham separação clara, e sombras bem aplicadas criam hierarquia visual. Isso ajuda a destacar elementos interativos como botões e modais.

Nosso tema já contempla isso com tokens definidos:

borderRadius: {
        small: "4px",
        medium: "8px"
    },
    boxShadow: {
        soft: `0px 2px 4px ${colors.black}1A`,
        medium: `0px 4px 8px ${colors.black}26`,
        strong: `0px 6px 12px ${colors.black}33`
    }
};

Em vez de colocar sombras aleatórias no código, utilizamos esses tokens para manter a consistência e garantir que todos os elementos tenham um visual alinhado e acessível.

No fim das contas, adotar acessibilidade usando design tokens não é só uma questão de boas práticas, mas também uma forma de criar interfaces mais amigáveis e que respeitam a diversidade das pessoas usuárias.

E o melhor? Depois de configurar tudo direitinho, sempre que você quiser ajustar ou melhorar algo fica mais fácil, mesmo que o projeto cresça.

Se você quiser saber mais sobre como criar conteúdos acessíveis e transformar a experiência das pessoas usuárias, confira o artigo Dicas para criar um conteúdo acessível para o seu produto.

Implementação com styled components

Agora que entendemos a importância do design tokens, vamos ver como usá-los na prática com Styled Components.

O Styled Components nos possibilita escrever estilos diretamente nos componentes utilizando JavaScript, sem precisar classes CSS separadas.

A principal vantagem é que os estilos ficam limitados a cada componente, evitando conflitos com outros estilos da aplicação.

Instalação do Styled Components

A instalação do styled-components é simples, basta rodar o seguinte comando no terminal:

# com npm
npm install styled-components

Ou

# com yarn
yarn add styled-components

Na documentação há outras dicas e formas de instalação.

Para que os estilos sejam aplicados corretamente em toda nossa aplicação, usaremos o ThemeProvider do styled-components, ele garante que todos os componentes tenham acesso aos valores definidos no tema.

Esse código precisa tá no ponto mais alto da nossa aplicação, ou seja, aquele que “envolve” todos os outros, então colocaremos no App.js:

import React, { useState } from "react";
import { ThemeProvider as StyledThemeProvider } from "styled-components";
import { lightTheme, darkTheme } from "./theme/theme"; 
import GlobalStyle from "./theme/GlobalStyle"; 
import Button from "./components/Button";
import ToggleButton from "./components/ToggleButton";
import styled from "styled-components";

const AppContainer = styled.main`
    background-color: ${({ theme }) => theme.colors.background.primary};
    min-height: 100vh;
    padding: ${({ theme }) => theme.padding.medium};
    color: ${({ theme }) => theme.colors.text.body};
    display: flex;
    flex-direction: column;
    gap: ${({ theme }) => theme.gap.medium};
`;

const ButtonContainer = styled.div`
  display: flex;
  gap: ${({ theme }) => theme.gap.medium};
  justify-content: center;
`;

function App() {
  const [isDarkMode, setIsDarkMode] = useState(false); 
  const theme = isDarkMode ? darkTheme : lightTheme; 

  const toggleTheme = () => setIsDarkMode(!isDarkMode); 

  return (
    <StyledThemeProvider theme={theme}> 
      <GlobalStyle />
      <AppContainer>
        <ButtonContainer>
          <ToggleButton onClick={toggleTheme}>
            {isDarkMode ? "Mudar para tema claro" : "Mudar para tema escuro"}
          </ToggleButton>
          <Button>Botão acessível</Button>
        </ButtonContainer>
      </AppContainer>
    </StyledThemeProvider>
  );
}

export default App;

Aqui nesse código, o ThemeProvider possibilita que a gente alterne entre os temas lightTheme e darkTheme. E, para garantir estilos consistentes, utilizamos GlobalStyle para definir configurações globais:

import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
  body {
    margin: 0;
    padding: 0;
    font-family: ${({ theme }) => theme.typography.fontFamily.primary};
    background-color: ${({ theme }) => theme.colors.background.primary};
    color: ${({ theme }) => theme.colors.text.body};
    transition: all 0.3s ease-in-out;
  }
`;

export default GlobalStyle;

E você sabe onde colocaremos o arquivo de GlobalStyle? Isso mesmo, na pasta theme onde já estão os outros arquivos relacionados ao tema.

Você já tá pegando o jeito!

Criando um botão acessível com tokens

Agora, vamos criar um botão acessível com tokens?

Personagem azul com um corte de cabelo estiloso, vestindo um cinto verde e vermelho, apertando um botão em um painel futurista. A palavra 'OK!' aparece na tela, indicando que a ação foi concluída com sucesso.

Já vimos que design tokens ajudam a estruturar o projeto, possibilitando que elementos como botões sigam um padrão definido, garantindo uniformidade no uso de cores, espaçamentos e tipografia.

Vamos criar dentro do src uma pasta chamada Button e dentro dela colocar o arquivo index.jsx onde colocaremos o código:

import styled from "styled-components";

const Button = styled.button`
  background-color: ${({ theme }) => theme.colors.secondary};
  color: ${({ theme }) => theme.colors.text.button}; 
  border: ${({ theme }) => theme.borderWidth.thin} solid ${({ theme }) => theme.colors.primary};
  border-radius: ${({ theme }) => theme.borderRadius.small};
  padding: ${({ theme }) => theme.padding.medium};
  cursor: pointer;
  transition: all 0.3s ease-in-out;

  &:hover {
    background-color: ${({ theme }) => theme.colors.secondary};
  }

  &:focus {
    outline: 2px solid ${({ theme }) => theme.colors.focus};
    outline-offset: 2px;
  }

  &:disabled {
    background-color: ${({ theme }) => theme.colors.disabled};
    cursor: not-allowed;
  }
`;

export default Button;

Uma das facilidades do Styled Components é a possibilidade de estilizar os elementos com base em props, como podemos ver no código acima. Isso significa que os estilos podem ser dinâmicos e adaptáveis, dependendo do tema ou das configurações passadas para o componente.

E, os design tokens que definimos lá no início estão sendo aplicados diretamente aqui, como theme.colors.secondary, theme.borderRadius.small e theme.padding.medium garantem que o botão siga um padrão visual consistente em toda a aplicação.

E a acessibilidade é pelo contraste no texto (theme.colors.text.button) e pela visibilidade ao navegar com o teclado (outline no :focus).

O efeito hover melhora a experiência da pessoa usuária ao indicar que o botão é interativo.

Usando essa estrutura, qualquer alteração na identidade visual pode ser feita diretamente no design tokens, sem a necessidade de modificar cada componente individualmente. E tudo isso deixa o código mais legível também.

Modo escuro com design tokens

Agora, vamos permitir que a pessoa usuária alterne entre os temas claro e escuro dinamicamente. E faremos isso criando um theme switcher (alternador de tema).

Primeiro, vamos criar um ThemeContext para gerenciar o estado do tema, ele vai armazenar o estado do tema e fazer com que a aplicação saiba qual deles está ativo.

Criaremos uma pasta dentro de src chamada context, e dentro dela criaremos o arquivo ThemeContext.jsx:

import React, { createContext, useState, useContext } from "react";
import { lightTheme, darkTheme } from "../theme/theme";
import { ThemeProvider as StyledThemeProvider } from "styled-components";

const ThemeContext = createContext();

export const useTheme = () => useContext(ThemeContext);

export const ThemeProvider = ({ children }) => {
    const [isDarkMode, setIsDarkMode] = useState(false);
    const toggleTheme = () => setIsDarkMode(!isDarkMode);

    const theme = isDarkMode ? darkTheme : lightTheme;

    return (
        <ThemeContext.Provider value={{ theme, isDarkMode, toggleTheme }}>
            <StyledThemeProvider theme={theme}>{children}</StyledThemeProvider>
        </ThemeContext.Provider>
    );
};

Nós criamos um contexto (ThemeContext) e usamos useState para controlar o tema. Quando a pessoa usuária alterna entre os modos, o estado muda e o ThemeProvider aplica o tema correto na aplicação.

Bom demais, né?

Agora a gente precisa de um botão para fazer essa troca de temas. Vamos criar uma pasta dentro de components chamada ToggleButton e dentro dela colocaremos o arquivo index.jsx

import styled from "styled-components";
import Button from "../Button";

const ToggleButton = styled(Button)`
  background-color: ${({ theme }) => theme.colors.background.primary};
  color: ${({ theme }) => theme.colors.primary};
  border: ${({ theme }) => theme.borderWidth.thin} solid ${({ theme }) => theme.colors.primary};
  border-radius: ${({ theme }) => theme.borderRadius.small};
  padding: ${({ theme }) => theme.padding.small};

  &:hover {
    background-color: ${({ theme }) => theme.colors.background.primary};  
    color: ${({ theme }) => theme.colors.primary}; 
  }
`;

export default ToggleButton;

Nessa parte, usamos styled-components para estilizar o botão e mais uma vez aplicamos os valores definidos no design tokens, como cores, espaçamentos e bordas.

Isso faz com que o botão siga a identidade visual da aplicação e se adapte automaticamente ao tema escolhido pela pessoa.

Vamos dá uma olhada no App.jsx agora, já que criamos o ThemeContext para gerenciar os temas e o ToggleButton para alterná-los, para ver como tudo isso se encaixa nele.

function App() {
  const { theme, isDarkMode, toggleTheme } = useTheme();

  return (
    <AppContainer theme={theme}>
      <ButtonContainer>
        <ToggleButton onClick={toggleTheme}>
          {isDarkMode ? "Mudar para tema claro" : "Mudar para tema escuro"}
        </ToggleButton>
        <Button>Botão acessível</Button>
      </ButtonContainer>
    </AppContainer>
  );
}

export default App;

Quando a pessoa usuária clica no botão, o ThemeProvider alterna entre os tokens do tema claro e escuro, mudando os estilos sem precisar recarregar a página.

Agora, em vez de gerenciar diretamente o tema dentro do App.jsx, usamos a context API com o ThemeContext, isso tira a responsabilidade do App de lidar com a lógica de alternância de tema, deixando-o focando apenas em exibir os componentes da interface.

Essa separação de responsabilidades é uma boa prática, pois mantém o código mais organizado. Ao final teremos esse resultado:

GIF mostrando uma interface web com dois botões. O primeiro botão tem o texto 'Mudar para tema escuro' e possui uma borda azul, enquanto o segundo botão, 'Botão acessível', é azul preenchido. Ao clicar o tema da página muda a cor para modo escuro.

Conclusão

Parabéns por chegar até aqui!

Quatro garotas em uniformes escolares, em estilo anime, batendo palmas com expressões felizes e satisfeitas. A cena transmite um sentimento de aprovação e entusiasmo.

Nós vimos como os design tokens deixam o projeto mais organizado e prático de usar. Com eles, dá para garantir que tudo fique sempre com a mesma cara, sem dor de cabeça na hora de atualizar ou adaptar para diferentes telas.

Outra coisa importante que vimos: acessibilidade não é algo que dá para deixar para depois. Quando já começamos pensando nisso, evitamos retrabalho e criamos algo que realmente funciona para todo mundo.

E agora, o próximo passo é integrar os tokens ao Storybook! Ele é usado para desenvolver e documentar componentes de forma isolada, ele possibilita a visualização e testes de componentes sem precisar rodar toda a aplicação.

Se quiser aprender mais sobre se liga nessa formação:

Bora seguir nessa evolução?

Referências

Lorena Garcia
Lorena Garcia

Lorena é formada em Análise e Desenvolvimento de Sistemas e, atualmente, faz parte do time de Suporte Educacional. Ama aprender coisas novas e dividir com outras pessoas. No tempo livre gosta de jogar games variados, ler livros e assistir animes.

Veja outros artigos sobre Front-end