Beaket Blog
Changelog

O Design System que diz não

O Design System que diz não

“O card deveria ter 4px ou 8px de border-radius?”

Foi uma thread de 47 minutos. Quatro engenheiros, dois designers, um arquivo no Figma com seis variantes. A discussão cobriu convenções do iOS, diretrizes do Material Design, alinhamento óptico e, em dado momento, a proporção áurea. Ninguém estava errado. Esse era o problema.

Já tínhamos passado por isso antes. Não só com border-radius — com direções de gradiente, valores de blur em sombras, níveis de opacidade para estados desabilitados. Cada decisão era pequena. Cada debate era razoável. E, juntos, estavam devorando nossa velocidade.

Então paramos de tentar encontrar a resposta certa. Removemos a pergunta.

O custo das opções

Toda propriedade visual em um design system é uma superfície de decisão. Só o border-radius já gera perguntas: botões e cards devem usar o mesmo valor? E elementos aninhados — o raio interno subtrai o externo? O raio muda em tamanhos diferentes?

Nenhuma dessas perguntas é difícil. É isso que as torna perigosas. São fáceis o bastante para que todo mundo tenha uma opinião, mas de impacto tão baixo que nenhuma opinião é demonstravelmente melhor. Bikeshedding clássico.

Catalogamos manualmente os comentários de PRs ao longo de um trimestre. Cerca de 30% das threads de revisão do design system eram sobre valores cosméticos sem nenhum impacto na usabilidade. Não layout. Não acessibilidade. Não padrões de interação. Só… estética.

Foi aí que escrevemos as regras.

Por que eliminar em vez de restringir

A alternativa óbvia é manter as propriedades, mas limitá-las. A maioria dos design systems maduros faz isso — três valores permitidos de border-radius, uma escala de elevação de sombras, uma paleta curada de gradientes. Consideramos essa opção.

O problema é que opções restritas ainda geram debates. “Esse card deveria usar radius-sm ou radius-md?” é a mesma conversa com menos respostas. Menos é melhor que muitas, mas zero é melhor que menos. Restringir uma propriedade reduz decisões. Eliminá-la remove a decisão inteiramente.

Também avaliamos linting — uma regra de ESLint que rejeita valores não autorizados. Mas um linter que impõe “use apenas 0, 4 ou 8px de raio” ainda deixa três escolhas. O objetivo não era tornar a decisão mais fácil. Era torná-la desnecessária.

Quatro regras

A biblioteca de componentes do Beaket tem quatro restrições visuais:

  1. Sem border-radius. Cantos retos em tudo. Botões, cards, inputs, dialogs, tooltips — tudo retangular.
  2. Sem gradientes. Apenas cores planas. Toda superfície é uma única variável CSS.
  3. Sem sombras com blur. Nenhum box-shadow com raio de desfoque. Apenas sombras com offset.
  4. Sem opacidade decorativa. Estados desabilitados usam mudança de cor, não transparência. Nada de atalhos com opacity: 0.5.

Essas não são diretrizes. São regras rígidas, aplicadas em code review e documentadas nas diretrizes dos componentes. Um PR que adiciona rounded-md é rejeitado.

Por que essas quatro propriedades especificamente? Porque eram as que geravam mais debate com menor impacto na usabilidade. Espaçamento e tipografia afetam a legibilidade. Cor afeta o significado. Mas a diferença entre 4px e 8px de border-radius? Entre uma sombra com blur de 4px e uma de 6px? Não conseguimos encontrar um único caso em que essas escolhas afetaram mensuravelmente a forma como alguém usava o produto.

Depois de adotar as regras, nossos PRs de design system começaram a andar mais rápido. As threads de revisão ficaram mais curtas. Os designers pararam de produzir múltiplas variantes do mesmo componente diferindo apenas no tratamento de cantos ou na profundidade da sombra. Os debates não diminuíram — desapareceram.

O que usamos no lugar

Remover opções não significa remover hierarquia visual. Você ainda precisa comunicar profundidade, estado e interação. Veja como substituímos cada propriedade.

Sombras com offset em vez de blur

Em vez de box-shadow: 0 4px 6px rgba(0,0,0,0.1), usamos sombras com offset rígido definidas como variáveis CSS:

--shadow-offset: 1px 1px 0px 0px var(--color-chrome);
--shadow-offset-hover: 2px 2px 0px 0px var(--color-chrome);
--shadow-offset-active: 0px 0px 0px 0px var(--color-chrome);

Três estados de interação. Zero blur. Esse é o sistema de sombras inteiro.

O estado padrão tem um offset de 1px — o elemento fica ligeiramente acima da superfície. O hover empurra para 2px — uma elevação sutil. O active encaixa em 0px — o elemento é pressionado para baixo. É uma animação pequena e mecânica que comunica a interação com clareza e não deixa nada para o desenvolvedor configurar.

Na prática, todo componente interativo aplica os mesmos três tokens. Aqui está a base do nosso componente de botão (simplificado da string CVA completa, que também inclui estados disabled e focus):

const buttonVariants = cva(
  [
    "inline-flex items-center justify-center gap-2",
    "font-medium cursor-pointer",
    "shadow-offset hover:shadow-offset-hover active:shadow-offset-active",
    "transition-shadow duration-100",
    // ... disabled, focus-visible, and icon styles
  ].join(" "),
  // ... variants
);

Nenhuma decisão sobre qual nível de sombra um componente “merece.”

Cores planas via variáveis CSS

Nossa paleta de cores — que chamamos de “Porcelain” — tem 14 tons neutros que vão de graphite (#030509) a paper (#ffffff). São frios e com tonalidade azulada. A descrição do tema na nossa documentação diz: “Precisão industrial. Gravado em painéis de controle em laca sintética preta. Branco puro, tinta azul-preta fria, sombras fantasmagóricas, destaque em teal.”

Toda referência de cor passa por um token semântico:

--color-text-primary: var(--color-graphite);
--color-text-secondary: var(--color-steel);
--color-bg-primary: var(--color-paper);
--color-border-default: var(--color-chrome);

Evitamos os padrões de cor do Tailwind como bg-white ou text-gray-500. Toda cor é uma variável nomeada, e toda variável mapeia para um papel. Ninguém debate se algo deveria ser gray-100 ou gray-200 — o token semântico já responde a pergunta.

Cantos retos em todo lugar

Esse não precisa de substituto. Você simplesmente… não adiciona border-radius.

O que nos surpreendeu foi como cantos retos interagem bem com sombras de offset. Um card arredondado com sombra de offset fica estranho — a borda rígida da sombra conflita com o canto suave. Mas um card com cantos retos e sombra de offset parece intencional. A geometria é consistente. As restrições se reforçam mutuamente de formas que não antecipamos completamente.

A linguagem visual se torna consistente por subtração. Quando tudo é reto, nada precisa justificar sua forma.

Onde as restrições se acumulam

O benefício mais nítido dessa abordagem aparece em questões transversais — os padrões que tocam todos os componentes.

Estados desabilitados são notoriamente inconsistentes em bibliotecas de componentes. Alguns usam opacidade, alguns mudam o fundo, alguns fazem os dois. Nós temos um único padrão, aplicado a todos os componentes:

disabled:shadow-none disabled:cursor-not-allowed disabled:border-dashed disabled:border-chrome disabled:bg-frost disabled:text-steel

Botões, inputs, selects, checkboxes, switches — todos idênticos. Sombra removida (a affordance interativa desaparece), borda tracejada, fundo frost, texto steel. Quando você viu um componente desabilitado, viu todos. As restrições removeram a tentação de fazer estados desabilitados “combinarem” com a personalidade visual de cada componente. Quando os componentes não têm personalidade visual, estados desabilitados convergem naturalmente.

Anéis de foco funcionam da mesma forma. Todo componente focalizável usa:

focus-visible:outline-2 focus-visible:outline-signal-blue focus-visible:outline-offset-2

Uma cor. Uma largura. Um offset. Um usuário de teclado navegando por um formulário vê exatamente o mesmo anel em cada elemento. Nenhum componente sobrescreve isso. Nenhuma variante remove isso. Combinado com o padrão uniforme de disabled, o usuário sempre sabe exatamente como “inativo” e “focado” se parecem — independentemente do componente com o qual está interagindo.

A única exceção

Radio buttons usam rounded-full.

Só isso. Essa é a única exceção em toda a biblioteca de componentes. Está documentada nas diretrizes dos componentes junto com as regras que ela quebra, porque até exceções devem ser decisões, não acidentes.

Um radio button quadrado confundiria os usuários. A forma circular está tão profundamente enraizada nas convenções de interface que removê-la seria sacrificar usabilidade por pureza estética. Decidimos que essa era uma troca ruim.

Você pode perguntar: se a usabilidade justifica uma exceção para radios, por que não para avatares, tags ou toggle switches? Testamos. Avatares quadrados com sombras de offset parecem cards de perfil — transmitem intencionalidade. Tags e toggles são menos atrelados a convenções do que radio buttons. A affordance circular é exclusivamente forte para radios; nada mais passou no crivo.

O objetivo nunca foi “brutalismo por si só.” Era remover decisões visuais que não justificam sua complexidade. Um radio button arredondado justifica sua forma. Um card arredondado não.

O que isso realmente custa

Seria desonesto dizer que essa abordagem não tem desvantagens. Tem.

“Parece inacabado.” Essa é uma reação real de testes com usuários. Cantos retos e cores planas não transmitem o mesmo acabamento que interfaces arredondadas e sombreadas. Tivemos que explicar o raciocínio mais de uma vez.

Alguns componentes parecem rígidos. Tooltips e popovers, em particular, ficam mais pesados com cantos retos. Sombras com blur existem por uma razão — criam uma sensação de flutuação que sombras de offset não replicam completamente.

Componentes de terceiros quebram o padrão. Date pickers, editores de rich text, bibliotecas de gráficos — todos vêm com border-radius e sombras com blur embutidos. Sobrescrevemos o que podemos e aceitamos alguma inconsistência nas bordas. Essa é uma tensão contínua, não um problema resolvido.

A expressão do designer é restringida. Se você é o tipo de designer que encontra prazer em criar micro-interações com overlays de gradiente e profundidade sutil, esse sistema vai parecer limitante. Esse é o objetivo — mas ainda assim é um custo real para a satisfação criativa.

É polarizante. Alguns engenheiros amam a simplicidade. Outros acham que é dogmático. Não resolvemos essa tensão completamente. Apenas decidimos que o benefício da consistência supera o custo estético para o nosso produto.

O que ainda não sabemos

Não testamos esse sistema em escala real — centenas de componentes, interfaces com alta densidade de dados, integrações com terceiros que precisam parecer nativas. Tabelas, gráficos e dashboards podem precisar de indicações de profundidade que sombras de offset não conseguem fornecer.

Estamos genuinamente incertos se os usuários desenvolvem o mesmo entendimento intuitivo dos nossos estados de sombra (padrão → hover → active) que têm com sistemas convencionais de elevação. Não observamos regressões de usabilidade, mas não conduzimos testes A/B formais especificamente sobre isso. Está funcionando até agora. Mas “até agora” é uma amostra pequena.

Restrições como decisões

Toda restrição neste sistema é uma decisão tomada uma vez para não precisar ser tomada de novo. Sem border-radius não é uma preferência estética — é um ADR (Architecture Decision Record) para toda a biblioteca de componentes.

Se sua equipe gasta tempo debatendo propriedades visuais que não afetam a usabilidade, considere remover a opção inteiramente. Você pode sentir menos falta do que imagina.

Share