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
Ligações
Apoia-se em
Contexto
Seção intitulada “Contexto”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
Opções consideradas
Seção intitulada “Opções consideradas”1. Tailwind CSS v4
Seção intitulada “1. Tailwind CSS v4”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/vitesuporta 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
2. CSS Modules
Seção intitulada “2. CSS Modules”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
3. Panda CSS / vanilla-extract
Seção intitulada “3. Panda CSS / vanilla-extract”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.
5. StyleX
Seção intitulada “5. StyleX”CSS-in-JS atómico da Meta. Contras: ainda pré-1.0 (0.19), ecossistema pequeno fora da Meta.
Camada de componentes
Seção intitulada “Camada de componentes”- 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 proprender - 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
Decisão
Seção intitulada “Decisão”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. Semtailwind.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-*emcomponents.json). Os componentes vivem emsrc/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 reencaminhamclassName - A ordenação de classes está ativa no Oxfmt via o
.oxfmtrc.jsonda raiz (ADR-0026):sortTailwindcsscomstylesheet: "./apps/web/src/styles/app.css"efunctions: ["cn", "cva", "clsx"], pelo que obun run checkimpõ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.
Consequências
Seção intitulada “Consequências”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 usamrendere 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.

