ADR 011: shadcn/ui como base de componentes
Contexto
El design system de A2R actualmente proporciona solo cuatro componentes custom (Button, Modal, ThemeProvider, Typography) construidos con CSS Modules. Construir componentes accesibles y listos para produccion desde cero es costoso: cada componente requiere cumplimiento WAI-ARIA, navegacion por teclado, gestion de foco, pruebas con lectores de pantalla y validacion cross-browser. Al ritmo actual, entregar los 60+ componentes que la plataforma necesita (dialogos, dropdowns, tooltips, tabs, command palettes, date pickers, etc.) llevaria anios.
Mientras tanto, el ecosistema frontend ha cambiado significativamente:
- Radix UI esta en un declive de facto. El co-creador original (Colm Tuite) llamo publicamente al proyecto "un pasivo" y paso a co-fundar Base UI en MUI. Solo un mantenedor (Chance Strickland) continua haciendo commits esporadicos. El repositorio tiene 774 issues abiertas, y el ultimo release sustancial fue en noviembre de 2025. No hay roadmap publico ni compromiso con el mantenimiento a largo plazo.
- Base UI surgio como sucesor. Creado por los autores originales de Radix en MUI,
@base-ui/reactalcanzo v1.0 en diciembre de 2025. Continua la filosofia de primitivos headless y sin estilos de Radix pero con respaldo corporativo activo, un equipo dedicado y un roadmap claro. Soporta React 19 y entrega componentes compatibles con WAI-ARIA de forma nativa. - shadcn/ui agrego soporte para Base UI en diciembre de 2025, proporcionando una ruta de migracion fuera de los primitivos de Radix. Los proyectos ahora pueden elegir entre Radix y Base UI como capa de primitivos subyacente.
- El stack existente es totalmente compatible. React 19, TypeScript 5, Tailwind CSS v4 y Rsbuild funcionan con shadcn/ui y Base UI sin configuracion adicional.
Decision
Adoptar shadcn/ui con Base UI (@base-ui/react ^1.2.0) como base de componentes para el design system de A2R. Estilo visual: base-lyra.
No es una dependencia -- codigo fuente propio
shadcn/ui no es una biblioteca de componentes instalada como paquete npm. En su lugar, los componentes se copian al proyecto como codigo fuente en src/components/ui/. El equipo es duenio de cada linea, puede modificar cualquier componente libremente y nunca se bloquea por ciclos de release upstream. Las actualizaciones son opt-in: el CLI puede hacer diff de cambios, pero nada se trae automaticamente.
Base UI para accesibilidad
@base-ui/react (^1.2.0) proporciona la capa de primitivos headless y sin estilos que maneja:
- Roles, estados y propiedades WAI-ARIA
- Navegacion por teclado y gestion de foco
- Anuncios para lectores de pantalla
- Interaccion de puntero y tactil
- Logica de cierre (tecla Escape, click fuera)
Los componentes construidos sobre Base UI heredan estos comportamientos sin que el equipo del design system necesite reimplementar patrones de accesibilidad para cada nuevo componente.
Por que Base UI sobre Radix
| Factor | Radix UI | Base UI |
|---|---|---|
| Mantenedores activos | 1 (esporadico) | Equipo dedicado en MUI |
| Respaldo corporativo | Ninguno (Modulz disuelto) | MUI (rentable, establecido) |
| Ultimo release | Noviembre 2025 | Releases mensuales activos |
| Issues abiertas | 774+ | Triaje activo |
| Soporte React 19 | Parcial | Completo |
| Recomendacion del creador | Co-creador se fue a Base UI | Creado por autores originales de Radix |
| Patron de composicion | prop asChild | prop render |
| Soporte shadcn/ui | Si (default original) | Si (desde diciembre 2025) |
El co-creador original de Radix (Colm Tuite) recomendo publicamente Base UI como la opcion de futuro. Elegir Base UI evita construir sobre un proyecto con mantenimiento incierto mientras se accede a la misma filosofia de diseno de los mismos creadores.
Integracion con Tailwind CSS v4
shadcn/ui usa Tailwind CSS para todo el estilado de componentes. Esto se alinea con la configuracion existente de Tailwind en la plataforma:
- Colores OKLCH: Tailwind v4 usa el espacio de color OKLCH por defecto, que proporciona manipulacion de color perceptivamente uniforme. El sistema de tokens de shadcn/ui mapea limpiamente a valores OKLCH.
- Configuracion CSS-first: La directiva
@themede Tailwind v4 reemplaza el antiguotailwind.config.js, y las variables de shadcn/ui se integran directamente en CSS. - Tailwind completo en el design system: El paquete del design system usa clases utilitarias de Tailwind exclusivamente para el estilado de componentes. Los archivos CSS Module (
.module.css) se eliminan del design system. Los equipos MFE siguen siendo libres de elegir su propio enfoque de estilado (CSS Modules, Tailwind, o ambos).
Estilo visual: base-lyra
shadcn/ui ofrece multiples estilos visuales. base-lyra se elige para A2R porque:
- Rectangular, bordes rectos: Sin esquinas redondeadas en la mayoria de elementos, creando una apariencia profesional y estructurada.
- Compatibilidad con fuentes serif display: Se combina naturalmente con los encabezados serif Faculty Glyphic de A2R.
- Alineacion con la marca profesional: La estetica geometrica y editorial encaja con la identidad de marca de A2R.
Integracion con la distribucion dual
El modelo de distribucion dual existente (tsup para tipos/tokens npm + Rsbuild para MF remote) permanece sin cambios. Los componentes shadcn/ui se exponen via Module Federation igual que los componentes custom existentes:
// rsbuild.config.ts exposes map (extracto)
exposes: {
"./Button": "./src/components/ui/button.tsx",
"./Dialog": "./src/components/ui/dialog.tsx",
"./Dropdown": "./src/components/ui/dropdown-menu.tsx",
// ... componentes shadcn adicionales
"./ThemeProvider": "./src/components/ThemeProvider/index.ts",
"./hooks": "./src/hooks/index.ts",
"./utils": "./src/utils/index.ts",
}
components.json para Rsbuild
shadcn/ui requiere un archivo de configuracion components.json en la raiz del paquete. Como el CLI no detecta automaticamente Rsbuild, se requiere setup manual:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "base-lyra",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
Decisiones clave de configuracion:
rsc: false: El design system es solo CSR (sin React Server Components).style: "base-lyra": Estilo visual rectangular, bordes rectos.cssVariables: true: Usa CSS custom properties para theming, integrandose con el sistema de tokens existente.
Mapeo de namespace de tokens
shadcn/ui define su propio namespace de CSS custom properties. Estos deben mapearse a los tokens --ds-* existentes del design system para mantener la consistencia:
| Variable shadcn | Mapea a | Proposito |
|---|---|---|
--background | --ds-bg-primary | Fondo de pagina |
--foreground | --ds-text-primary | Color de texto por defecto |
--primary | --ds-color-primary-600 | Color primario de marca |
--primary-foreground | #FFFFFF | Texto sobre primario |
--secondary | --ds-bg-secondary | Fondos secundarios |
--secondary-foreground | --ds-text-primary | Texto sobre secundario |
--muted | --ds-color-neutral-100 | Fondos atenuados |
--muted-foreground | --ds-text-muted | Texto atenuado |
--accent | --ds-color-primary-50 | Acentos |
--accent-foreground | --ds-color-primary-900 | Texto sobre acento |
--destructive | --ds-color-error-500 | Acciones destructivas |
--border | --ds-border-default | Bordes por defecto |
--input | --ds-border-default | Bordes de inputs |
--ring | --ds-color-primary-500 | Anillos de foco |
--radius | 0px | Border radius (base-lyra: recto) |
Actualizacion de cn()
La utilidad cn() existente se actualiza para usar clsx + tailwind-merge para un manejo robusto de nombres de clase:
// src/lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
tailwind-merge (^3.5.0) resuelve inteligentemente conflictos de clases Tailwind (por ejemplo, p-4 vs p-2), asegurando que los consumidores de componentes puedan sobrescribir estilos sin comportamientos de cascada inesperados.
class-variance-authority
class-variance-authority (CVA, ^0.7.1) proporciona definiciones de variantes type-safe para componentes:
import { cva, type VariantProps } from "class-variance-authority";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
secondary: "bg-secondary text-secondary-foreground border border-border hover:bg-accent",
ghost: "hover:bg-accent hover:text-accent-foreground",
danger: "bg-destructive text-white hover:bg-destructive/90",
},
size: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base",
},
},
defaultVariants: {
variant: "primary",
size: "md",
},
}
);
CVA asegura que las combinaciones de variantes se verifiquen en tiempo de compilacion y que las combinaciones invalidas produzcan errores de TypeScript.
Ruta de migracion
La migracion de los 4 componentes CSS Modules actuales a shadcn/ui + Base UI sigue esta ruta:
- Nuevos componentes: Se agregan via el CLI de shadcn, se personalizan y se exponen a traves de Module Federation.
- Componentes existentes reconstruidos: Button, Modal y Typography se reconstruyen usando equivalentes de shadcn/ui con CVA + Base UI + clases utilitarias Tailwind. Los archivos CSS Module (
.module.css) se eliminan. - ThemeProvider permanece custom: El ThemeProvider no es un componente de shadcn. Permanece custom pero se actualiza para tambien establecer las variables CSS de shadcn junto con los tokens
--ds-*existentes.
Flujo de trabajo para agregar componentes
# 1. Agregar un componente shadcn
npx shadcn@latest add dialog
# 2. El componente se coloca en src/components/ui/dialog.tsx
# 3. Personalizar: ajustar tokens, variantes, estilos de marca
# 4. Exponer via Module Federation en rsbuild.config.ts
# "./Dialog": "./src/components/ui/dialog.tsx"
# 5. Agregar stories de Storybook
# src/components/ui/dialog.stories.tsx
# 6. Exportar tipos desde el entry point del paquete npm
Consecuencias
Positivas
- 60+ componentes accesibles disponibles inmediatamente, cubriendo dialogos, dropdowns, tooltips, tabs, command palettes, popovers, sheets y mas.
- Codigo fuente propio: Los componentes viven en
src/components/ui/, totalmente modificables sin esperar releases upstream. - Base UI mantenido activamente: Equipo dedicado en MUI, releases regulares, roadmap claro, soporte React 19.
- Alineacion con Tailwind: Integracion completa con Tailwind CSS v4 con colores OKLCH y configuracion CSS-first.
- Compatibilidad con React 19: Base UI esta construido para React 19, sin shims de compatibilidad necesarios.
- Soporte RTL: Los primitivos de Base UI soportan layouts right-to-left de forma nativa.
- 5 estilos visuales: Los equipos pueden evaluar estilos alternativos (default, new-york, miami, base-lyra, base-pico) para diferentes contextos de producto.
- Variantes type-safe: CVA proporciona verificacion en tiempo de compilacion de combinaciones de variantes de componentes.
Negativas
- Ecosistema Base UI menos maduro que Radix: Menos recursos comunitarios, tutoriales de terceros y codemods comparado con el ecosistema establecido de Radix. El tooling aun esta creciendo.
renderprop en vez deasChild: Base UI usa unrenderprop para composicion de componentes en lugar del patronasChildde Radix. Los desarrolladores familiarizados con Radix necesitaran aprender el nuevo patron.- Setup manual de Rsbuild: El CLI de shadcn no detecta automaticamente Rsbuild como framework, requiriendo configuracion manual de
components.json. - Nuevas dependencias: Seis nuevas dependencias runtime/dev (
@base-ui/react,class-variance-authority,clsx,tailwind-merge,lucide-react,tw-animate-css). - Esfuerzo de mapeo de tokens: El namespace de variables CSS de shadcn debe mapearse al sistema de tokens
--ds-*existente. Este es un costo de configuracion unico. - Issue conocido: La directiva
"use client"en el componente Button puede causar problemas en ciertas configuraciones de build (shadcn/ui #9428). Workaround: asegurarse de que la configuracion de Rsbuild no elimine directivas client.
Nuevas dependencias
| Paquete | Version | Proposito |
|---|---|---|
@base-ui/react | ^1.2.0 | Primitivos headless accesibles |
class-variance-authority | ^0.7.1 | Variantes de componentes type-safe |
clsx | ^2.1.1 | Nombres de clase condicionales |
tailwind-merge | ^3.5.0 | Resolucion de conflictos de clases Tailwind |
lucide-react | ^0.576.0 | Biblioteca de iconos |
tw-animate-css | ^1.4.0 | Utilidades de animacion para Tailwind v4 |
Alternativas consideradas
shadcn/ui + Radix UI
El default original de shadcn/ui. Radix proporciona excelentes primitivos de accesibilidad y ha sido el estandar para bibliotecas de componentes headless. Sin embargo, con el proyecto en declive de facto -- un unico mantenedor esporadico restante, 774+ issues abiertas, sin roadmap, y la partida publica del co-creador -- construir la base de componentes a largo plazo de la plataforma sobre Radix conlleva un riesgo de mantenimiento inaceptable.
Primitivos Radix UI solos (sin shadcn)
Usar primitivos de Radix directamente y construir todo el estilado desde cero. Este enfoque requiere significativamente mas esfuerzo para crear componentes estilados y ricos en variantes. Tambien esta sujeto a las mismas preocupaciones de mantenimiento de Radix descritas arriba, sin el beneficio de las implementaciones pre-construidas de componentes y el tooling CLI de shadcn/ui.
Material UI (MUI)
La biblioteca de componentes de MUI es completa y bien mantenida. Sin embargo, su enfoque CSS-in-JS (Emotion) entra en conflicto con la estrategia Tailwind CSS de la plataforma. Los componentes de MUI se instalan como dependencias npm, no codigo fuente propio, limitando la personalizacion. La inyeccion de estilos en runtime tambien agrega overhead que el enfoque utility-first de Tailwind evita.
Chakra UI
Chakra UI proporciona una biblioteca de componentes accesible con buena experiencia de desarrollador. Sin embargo, usa Panda CSS para el estilado, lo cual entra en conflicto con la inversion en Tailwind CSS de la plataforma. Los style props runtime de Chakra agregan overhead comparado con las clases utilitarias en tiempo de compilacion de Tailwind.
Headless UI (Tailwind Labs)
Headless UI es construido por el equipo de Tailwind Labs y se integra naturalmente con Tailwind CSS. Sin embargo, proporciona solo ~10 componentes comparado con los 35+ de shadcn/ui. Carece de implementaciones pre-estiladas, requiriendo que el equipo construya todas las variantes visuales desde cero.
Construir desde cero
Continuar construyendo componentes desde cero al ritmo actual (4 componentes a la fecha). Este enfoque es insostenible: la experiencia en accesibilidad requerida para cada componente es costosa, el tiempo de desarrollo es prohibitivo, y los equipos de plataforma necesitan 60+ componentes para ser productivos.
Documentacion relacionada
- Vision General de Arquitectura -- Stack tecnologico y contexto del sistema
- Design System -- Distribucion dual, theming, detalles de integracion de componentes
- Module Federation -- Distribucion de componentes como MF remote
- ADR-008: Rsbuild Bundler -- Herramienta de build para salida MF
- Desarrollo Local -- Flujo de trabajo de desarrollo local del design system