Wendeson Gomes
← Voltar pro blogDesign system

Como suportar 3 design systems sem espalhar if pelo código

29 out 20266 min de leiturapor Wendeson Gomes

Um mesmo produto pode ter três caras diferentes. Cada ambiente escolhe o design system dele, como Material (Google), Fluent (Microsoft) ou Bootstrap. A tela é a mesma. O botão, o campo e o diálogo mudam.

O caminho ingênuo é o que todo mundo tenta primeiro: um if espalhado pelas telas, algo como "se for Material, renderiza este botão, senão aquele". Funciona na primeira semana. Na décima, cada tela conhece três mundos e qualquer ajuste visual vira uma caça a ifs.

A regra que segurou tudo foi simples: os apps e os módulos não sabem qual design system está ativo. Eles pedem um Button, um Combobox, um Dialog. Quem decide como aquilo aparece é um pacote de UI, e a diferença entre os sistemas vira um override lá dentro, nunca um isMaterial() numa tela. Troque o design system abaixo: o código da tela não muda uma linha.

pacote de UI

<Button />decide qual desenhar

o cliente vê o tema dele

Nova solicitação

Maria Souza
Nome
Aprovação de compras▾
Processo
CANCELARSALVAR
o código da tela é o mesmo nos trêsmaterial
<Field label={t('name')} />
<Combobox label={t('process')} />
<Checkbox>{t('notify')}</Checkbox>
<Button>{t('save')}</Button>

Toque para trocar. A tela não muda uma linha.

O que o pacote resolve uma vez

  • Só o ativo é baixadoCada sistema vive no próprio módulo, carregado por import(). Quem usa Material nunca baixa o Fluent nem o Bootstrap.
  • Acessibilidade no componenteFoco visível, contraste, teclado, papéis ARIA e prefers-reduced-motion ficam no pacote, não em cada tela.
  • Tema, escuro e RTL por tokensTrocar de sistema, de modo ou de direção é trocar valores, sem nenhum if na tela.

Design system ativo: Material. Só ele foi carregado.

Três hábitos ajudam a manter essa fronteira. Sem cor nova: tudo vem de tokens em CSS custom properties, então trocar de tema é trocar valores, não classes. Se só uma tela quebra num sistema, conserto a tela e não o componente compartilhado, porque mexer no compartilhado para agradar um consumidor quebra os outros dois sistemas. E todo z-index fica contido, porque três bibliotecas empilhando camadas é receita para um modal atrás de outro.

Outro ponto que costuma ser esquecido é o peso. Cada design system mora no próprio módulo e é carregado sob demanda, com import dinâmico. Se o ambiente usa Material, o navegador nunca baixa o código dos outros dois. Sem isso, suportar três sistemas significaria entregar os três para todo mundo.

A acessibilidade também fica no pacote, não nas telas. Foco visível, contraste suficiente, navegação por teclado, papéis ARIA corretos e respeito a prefers-reduced-motion são resolvidos uma vez, dentro do componente. Como tema, modo escuro e direção chegam pelo mesmo lugar, todos herdam o mesmo cuidado.

Mesmo com a fronteira certa, cada biblioteca tem suas manhas. Três que me custaram tempo: o register do react-hook-form não grava valor em componentes que não são inputs nativos, é preciso usar Controller; padding passado por className cai fora da borda quando a biblioteca desenha dentro de um shadow DOM; e uma lib headless (como o Ark UI) e o MUI brigam pelo focus trap quando um diálogo abre dentro do outro.

Modo escuro e árabe (RTL) entram na mesma conta: viram mais um eixo dentro do pacote de UI, não mais um if na tela. Aperte Escuro e RTL na demonstração para ver.

A fronteira entre o que a tela quer e como o sistema desenha é o que paga a conta de ter três temas.
Próximo
Como deixei meu portfólio redondo pro Google
→