Pular para o conteúdo

ADR-0011 Estilos

Estilos padrão da empresa — Tailwind CSS v4, com componentes shadcn/ui sobre primitivas Base UI.

Decisão
Tailwind CSS v4 + shadcn/ui sobre Base UIConstruir a app
Estado
Aceite
Data
Âmbito
Predefinição da empresa para todas as novas apps

O React (ADR-0001) deixa os estilos em aberto. Precisamos de uma única forma de escrever estilos e de um conjunto inicial de componentes acessíveis, para que os produtos partilhem design tokens, componentes e hábitos de revisão em vez de cada equipa reinventar botões e diálogos.

Requisitos:

  • Funciona com streaming SSR no TanStack Start (ADR-0002) — sem injeção de estilos em runtime no caminho de renderização
  • Encaixa na toolchain (ADR-0035) — plugin Vite, suporte de formatação no Oxfmt, sem ferramenta paralela Prettier
  • Design tokens (cor, espaçamento, raio, tipografia) definidos uma só vez e personalizáveis por tema (modo escuro, marca por produto)
  • Primitivas acessíveis — gestão de foco, suporte de teclado, ARIA para diálogos, menus, popovers e selects — não construídas à mão em cada produto

CSS utilitário (utility-first). A v4 moveu a configuração para o CSS (@theme) e inclui um plugin Vite.

Prós

  • O maior ecossistema; o alvo predefinido dos kits de componentes (shadcn/ui), da documentação e das ferramentas de programação com IA
  • Zero runtime: compila para uma única folha de estilos estática — ideal para streaming SSR
  • @tailwindcss/vite suporta o Vite 8; os tokens vivem no CSS como variáveis, pelo que a personalização por tema é CSS simples
  • O Oxfmt ordena nativamente as classes Tailwind (sortTailwindcss) — não é preciso plugin Prettier
  • Os estilos vivem junto do markup; sem nomes a inventar, sem ficheiros CSS mortos

Contras

  • Risco de manutenção: a Tailwind Labs despediu 75% da equipa de engenharia em janeiro de 2026 (quebra de receitas causada pela IA); seguiram-se novos patrocinadores (Vercel, Google AI Studio) e a v4 continua a ser publicada. Licença MIT e resultado em CSS simples — uma paragem congelar-nos-ia, não nos deixaria encalhados
  • Strings de classes longas; precisa de cn() e de helpers de variantes para se manter legível

Ficheiros .module.css com âmbito local, integrados no Vite.

Prós

  • Zero dependências, CSS simples, nada para aprender

Contras

  • Sem sistema partilhado de tokens ou utilitários — cada produto reinventa escalas de espaçamento e convenções
  • Nenhum ecossistema de kits de componentes assente nisto

Estilos com tipos seguros em TypeScript, extraídos em build time.

Prós

  • Tokens e variantes verificados por tipos; zero runtime
  • Ambos ativamente mantidos

Contras

  • Ecossistemas mais pequenos; o shadcn/ui e a maioria dos kits têm como alvo o Tailwind
  • Um passo extra de extração em build time e uma DSL própria para aprender

4. CSS-in-JS em runtime (styled-components, Emotion)

Seção intitulada “4. CSS-in-JS em runtime (styled-components, Emotion)”

Prós: familiar a muitos programadores React. Contras: injeta estilos durante a renderização — um custo com streaming SSR; o Emotion não publica uma versão desde 2024-12; o ecossistema migrou para abordagens de zero runtime.

CSS-in-JS atómico da Meta. Contras: ainda pré-1.0 (0.19), ecossistema pequeno fora da Meta.

  • shadcn/ui — os componentes são copiados para o nosso repositório pela sua CLI e estilizados com Tailwind; o código é nosso. Passou a assentar em primitivas Radix ou Base UI
  • Base UI (@base-ui/react, 1.x estável) — primitivas acessíveis sem estilos, da equipa por trás do Radix, do MUI e do Floating UI; compõe-se com uma prop render
  • Radix — as primitivas originais do shadcn/ui, ainda mantidas; mais blocos da comunidade; compõe-se com asChild
  • React Aria Components (Adobe) — a melhor cobertura de acessibilidade e i18n, mas não é uma opção do shadcn/ui
  • Bibliotecas empacotadas (MUI, Mantine, Chakra) — arranque rápido, mas acabamos a estilizar à volta do design system deles em vez do nosso

Tailwind CSS v4 para os estilos e shadcn/ui sobre Base UI como camada inicial de componentes são o padrão da empresa para todas as novas apps.

Em concreto:

  • Tailwind via @tailwindcss/vite, configurado numa única folha de estilos (src/styles/app.css): @import "tailwindcss" mais tokens @theme. Sem tailwind.config.js
  • Os tokens são variáveis CSS (--background, --foreground, --primary, … do shadcn/ui, mapeadas através de @theme inline). O modo escuro e a marca por produto trocam variáveis, não classes
  • shadcn/ui com um estilo Base UI (base-* em components.json). Os componentes vivem em src/components/ui/ e somos nós que os editamos. As primitivas vêm de @base-ui/react — nunca do obsoleto @base-ui-components/react
  • Os helpers de classes seguem as predefinições do shadcn/ui: cn() (clsx + tailwind-merge) para combinar, class-variance-authority (cva) para variantes. Os componentes aceitam e reencaminham className
  • A ordenação de classes está ativa no Oxfmt via o .oxfmtrc.json da raiz (ADR-0026): sortTailwindcss com stylesheet: "./apps/web/src/styles/app.css" e functions: ["cn", "cva", "clsx"], pelo que o bun run check impõe a ordem
  • Sem CSS-in-JS em runtime e sem um segundo sistema de estilos na mesma app. Ficheiros CSS simples são aceitáveis para o que os utilitários não conseguem exprimir (overrides de widgets de terceiros, keyframes complexas)

Os fatores decisivos: zero runtime no caminho de SSR, o maior ecossistema, suporte nativo na nossa toolchain (plugin Vite, ordenação no Oxfmt) e uma camada de componentes que possuímos em vez de alugarmos. Base UI em vez de Radix pelas versões estáveis ativamente publicadas e pela composição render mais limpa. Aceitamos o risco de manutenção da Tailwind Labs, mitigado pela licença MIT e pelo resultado em CSS simples.

Mais fácil:

  • Um só conjunto de tokens e um só ponto de partida de componentes entre produtos — as mudanças de design são edições de variáveis
  • Diálogos, menus, popovers e selects acessíveis vêm do Base UI em vez de esforço por equipa
  • Os componentes são código simples no repositório: alterar qualquer coisa sem lutar com a API de uma biblioteca
  • A formatação e a ordem das classes são impostas pelo bun run check, pelo que as revisões dispensam pormenores de estilo

Mais difícil:

  • Os componentes shadcn/ui são copiados, não instalados: as correções upstream só chegam quando as puxamos com a CLI e revemos o diff
  • Partilhar componentes entre produtos exige um plano — um registry shadcn/ui da empresa é o passo natural seguinte quando dois produtos duplicarem o mesmo componente
  • A maioria dos blocos e exemplos da comunidade shadcn/ui assume Radix (asChild); as versões Base UI usam render e precisam de adaptação
  • Acompanhar a saúde das versões do Tailwind; se a v4 estagnar, a saída é congelar a versão, não reescrever

Do que abdicamos: dos estilos verificados por tipos do Panda CSS / vanilla-extract e da máxima profundidade de acessibilidade do React Aria. Aceite — o alcance do ecossistema e a simplicidade de zero runtime pesam mais numa predefinição da empresa; um produto com necessidades fortes de i18n ou de acessibilidade pode escrever um ADR de exceção para o React Aria.