Novo Livro!
Categorias
Projetos

O problema de contraste dos botões que a maioria dos design systems ignora

O problema de contraste dos botões que a maioria dos design systems ignora

A maioria dos design systems define as cores de fundo dos botões. Mas muitos deles esquecem da outra metade do botão: a cor do texto que precisa ser legível sobre esse fundo. É aí que os problemas de contraste começam.

Um botão primário pode parecer perfeito hoje:

.button-primary {
background: var(--color-primary);
color: white;
}

Isso funciona enquanto --color-primary for um azul escuro, um roxo escuro ou qualquer outra cor escura da marca.

Mas o que acontece quando a cor primária muda?

:root {
--color-primary: #facc15;
}

Agora o fundo do botão é amarelo. Mas o texto continua branco.

O fundo mudou. O texto não.

Esse é o problema de contraste que muitos desenvolvedores deixam passar.

Tokens de background não são suficientes

Uma cor de fundo, sozinha, não conta toda a história de acessibilidade.

Este token está incompleto:

--color-primary: #2563eb;

Ele diz qual deve ser o fundo do botão, mas não diz qual cor de texto deve aparecer sobre ele. Então, os desenvolvedores chutam.

Geralmente escrevem:

color: white;

Isso cria uma suposição escondida:

Fundos primários sempre serão escuros o suficiente para texto branco.

Mas design systems mudam. Marcas mudam. Temas mudam. O dark mode muda.

Cores de warning, success, danger e secondary podem se comportar de formas completamente diferentes.

É por isso que todo token de fundo preenchido deveria ter um parceiro de texto legível.

Use pares de foreground

Uma estrutura mais segura é assim:

:root {
--color-primary: #2563eb;
--color-on-primary: #ffffff;
--color-danger: #dc2626;
--color-on-danger: #ffffff;
--color-warning: #facc15;
--color-on-warning: #111827;
--color-success: #16a34a;
--color-on-success: #ffffff;
}

Agora cada papel (role) de fundo tem um papel de texto que pertence a ele.

O componente não decide o par de contraste.

O tema decide.

.button-primary {
background: var(--color-primary);
color: var(--color-on-primary);
}
.button-danger {
background: var(--color-danger);
color: var(--color-on-danger);
}
.button-warning {
background: var(--color-warning);
color: var(--color-on-warning);
}

É uma pequena mudança de nomenclatura, mas ela muda a responsabilidade do sistema.

Em vez de cada componente chutar a cor do texto, os tokens do design system define a relação legível.

O exemplo do botão de warning

Botões de warning são um lugar comum onde esse problema aparece.

Muitos desenvolvedores escrevem:

.button-warning {
background: #facc15;
color: white;
}

Visualmente, isso pode parecer limpo. Mas amarelo costuma ser uma cor clara.

Branco também é claro. Então o texto pode ficar difícil de ler.

Um par de warning melhor é:

:root {
--color-warning: #facc15;
--color-on-warning: #111827;
}

Depois:

.button-warning {
background: var(--color-warning);
color: var(--color-on-warning);
}

O fundo continua comunicando alerta, mas o texto permanece legível.

Isso também ajuda no dark mode

Pares de foreground se tornam ainda mais úteis quando os temas mudam.

:root {
--color-primary: #2563eb;
--color-on-primary: #ffffff;
}
:root[data-theme="dark"] {
--color-primary: #60a5fa;
--color-on-primary: #0f172a;
}

O componente do botão não muda:

.button-primary {
background: var(--color-primary);
color: var(--color-on-primary);
}

Só os valores dos tokens mudam. Esse é o benefício dos tokens semânticos.

Componentes mantêm um contrato estável. Temas controlam o par legível.

Bons tokens de botão descrevem relações

Um sistema de tokens de botão mais forte deveria incluir pares como:

--color-primary
--color-on-primary
--color-secondary
--color-on-secondary
--color-danger
--color-on-danger
--color-success
--color-on-success
--color-warning
--color-on-warning

O token on-* significa:

Esta é a cor usada sobre esse fundo.

Essa ideia simples evita muitos erros de contraste.

Ela também torna o design system mais fácil de usar para os desenvolvedores.

Eles não precisam mais perguntar “Esse texto do botão deveria ser branco ou escuro?”. O token já responde.

E, se o objetivo for automatizar essa decisão diretamente no CSS, vale conhecer a função contrast-color(), que calcula um texto preto ou branco legível a partir da cor de fundo, sem depender de tokens definidos manualmente.

O problema real

O problema não é que os desenvolvedores escolhem cores ruins.

O problema é que muitos design systems definem só metade da relação.

Eles definem o fundo, mas deixam o texto por conta do componente.

Isso cria botões inconsistentes, temas frágeis e problemas de acessibilidade que aparecem mais tarde.

Uma regra melhor é:

Todo token de background com significado deveria ter um token de texto legível correspondente.

Especialmente para estados de UI preenchidos como:

primary
secondary
danger
success
warning
info
disabled
selected
inverse

Conclusão

Acessibilidade de botão não é só escolher uma boa cor de fundo.

É definir a cor do texto que pertence a esse fundo.

Então, em vez disso:

background: var(--color-primary);
color: white;

Prefira isto:

background: var(--color-primary);
color: var(--color-on-primary);

A primeira versão presume o contraste; a segunda, projeta o contraste.

Esse é o problema de contraste de botão que a maioria dos design systems ignora.