ADR 011: shadcn/ui como base de componentes

AceptadoFecha: 2026-03-03

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/react alcanzo 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.

shadcn/ui + Base UI como base de componentes shadcn/ui + Base UI como base de componentes Positivo 60+ componentes accesibles, codigo propio Base UI mantenido activamente (respaldo MUI) Alineacion completa con Tailwind CSS v4 + OKLCH Compatible con React 19, soporte RTL, 5 estilos Negativo Ecosistema Base UI menos maduro que Radix render prop en vez de asChild (patron diferente) Setup manual para Rsbuild (CLI no lo detecta) Nuevas dependencias + esfuerzo de mapeo de tokens

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

FactorRadix UIBase UI
Mantenedores activos1 (esporadico)Equipo dedicado en MUI
Respaldo corporativoNinguno (Modulz disuelto)MUI (rentable, establecido)
Ultimo releaseNoviembre 2025Releases mensuales activos
Issues abiertas774+Triaje activo
Soporte React 19ParcialCompleto
Recomendacion del creadorCo-creador se fue a Base UICreado por autores originales de Radix
Patron de composicionprop asChildprop render
Soporte shadcn/uiSi (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 @theme de Tailwind v4 reemplaza el antiguo tailwind.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 shadcnMapea aProposito
--background--ds-bg-primaryFondo de pagina
--foreground--ds-text-primaryColor de texto por defecto
--primary--ds-color-primary-600Color primario de marca
--primary-foreground#FFFFFFTexto sobre primario
--secondary--ds-bg-secondaryFondos secundarios
--secondary-foreground--ds-text-primaryTexto sobre secundario
--muted--ds-color-neutral-100Fondos atenuados
--muted-foreground--ds-text-mutedTexto atenuado
--accent--ds-color-primary-50Acentos
--accent-foreground--ds-color-primary-900Texto sobre acento
--destructive--ds-color-error-500Acciones destructivas
--border--ds-border-defaultBordes por defecto
--input--ds-border-defaultBordes de inputs
--ring--ds-color-primary-500Anillos de foco
--radius0pxBorder 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:

  1. Nuevos componentes: Se agregan via el CLI de shadcn, se personalizan y se exponen a traves de Module Federation.
  2. 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.
  3. 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.
  • render prop en vez de asChild: Base UI usa un render prop para composicion de componentes en lugar del patron asChild de 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

PaqueteVersionProposito
@base-ui/react^1.2.0Primitivos headless accesibles
class-variance-authority^0.7.1Variantes de componentes type-safe
clsx^2.1.1Nombres de clase condicionales
tailwind-merge^3.5.0Resolucion de conflictos de clases Tailwind
lucide-react^0.576.0Biblioteca de iconos
tw-animate-css^1.4.0Utilidades 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