Durante mucho tiempo traté nuestra librería de componentes como una carpeta compartida: un sitio donde dejar botones y modales para no repetirlos. El día que varios portales del monorepo empezaron a depender de ella al mismo tiempo, entendí que ya no era una carpeta. Era un producto, con usuarios reales —el resto del equipo— que sufren cada cambio mal pensado. Desde entonces la mantengo como Tech Lead en JXBS con esa mentalidad: mis usuarios son otros desarrolladores, y su experiencia importa tanto como la del usuario final.
Los tokens son el contrato, no un detalle
Antes de hablar de componentes hay que hablar de tokens, porque son lo primero que rompes sin darte cuenta. Un token es una decisión de diseño con nombre estable: un color, un espaciado, una escala tipográfica. Si un portal escribe #2563eb a mano y otro usa var(--color-primary), no tienes un design system, tienes dos sistemas que coinciden por accidente.
Yo trato los tokens como el contrato público. Los consumidores no piden colores en hexadecimal; piden intención semántica.
:root {
/* Primitivos: no se usan directamente en componentes */
--blue-600: #2563eb;
--gray-900: #111827;
--space-2: 0.5rem;
--space-4: 1rem;
/* Semánticos: esto es lo que el consumidor toca */
--color-action: var(--blue-600);
--color-text-strong: var(--gray-900);
--radius-control: 0.5rem;
}
[data-theme="dark"] {
--color-text-strong: #f9fafb;
}
La capa semántica es lo que me permite hacer theming sin tocar un solo componente. Cambio el mapeo, no la firma. Y esa separación entre primitivos y semánticos es la que evita que alguien acople su portal a un azul concreto que mañana quiero mover.
La API del componente es una promesa
Con React 19 y TypeScript, la firma de un componente es un contrato tan serio como el de una API HTTP. Cada prop que expongo es algo que tendré que sostener durante versiones. Por eso prefiero composición sobre configuración: en lugar de un <Card> con veinte props booleanas, expongo piezas que se combinan.
// Mal: configuración que crece sin fin
<Card title="..." footer withBorder elevated dense hasIcon />
// Mejor: composición, el consumidor arma su caso
<Card>
<Card.Header>...</Card.Header>
<Card.Body>...</Card.Body>
<Card.Footer>...</Card.Footer>
</Card>
Dos reglas que no negocio. La primera: no filtrar el DOM interno. Si expongo un className que se pega a un <div> cualquiera y mañana ese <div> desaparece, rompo a todo el que dependía de esa estructura. Prefiero exponer puntos de extensión explícitos (asChild, slots, variantes tipadas) antes que dejar que la gente estilice mis entrañas. La segunda: las props estables son sagradas. Renombrar variant a kind "porque suena mejor" no es un refactor, es romper a seis equipos.
💡 Un design system no se mide por cuántos componentes tiene, sino por cuánta gente puede construir encima sin escribirte un mensaje.
Versionar dentro del monorepo sin hacer daño
Turborepo y pnpm hacen que todo conviva, y eso tiene una trampa: es demasiado fácil "subir todo" a la vez. Yo uso changesets con semver de verdad. Un cambio de color semántico es un patch. Una prop nueva opcional es un minor. Quitar una prop, renombrarla o cambiar el DOM que alguien podría estar seleccionando es un major, y un major exige aviso, guía de migración y a veces un periodo de convivencia con la versión anterior.
Lo que evito religiosamente es el "bump everything" reflejo. Si toco solo el Button, no quiero forzar a que un portal que ni lo usa se recompile y revalide. El versionado granular es lo que mantiene la confianza: cuando actualizas, sabes qué te va a doler antes de que duela.
Accesibilidad de fábrica y el lado social
En Acid Tango, en Madrid, empujé WCAG 2.1 AA como estándar de equipo, y esa experiencia cambió cómo construyo librerías. La mejor accesibilidad es la que el consumidor recibe gratis. Si mi Dialog gestiona el foco, el Escape, el aria-modal y el retorno del foco al disparador, entonces cada portal es accesible sin que su desarrollador sepa nada de ARIA. El teclado, el manejo de foco y el contraste no son features opcionales: son el suelo.
Pero la parte más difícil no es técnica, es social. Un design system tiene gobernanza aunque nadie la escriba. Yo intento que sea explícita: quién decide qué entra, cómo se propone un componente nuevo, y —lo más incómodo— cómo se dice que no. Digo que no cuando algo sirve a un solo portal; eso vive en el portal, no en el core. Para onboarding de un componente nuevo pido tres cosas: un caso de uso real repetido, documentación con ejemplos copiables, y accesibilidad resuelta antes de mezclar. Sin ejemplos, la gente reinventa; y cada reinvención es una grieta en el contrato.
Qué haría distinto en 2026
Documentaría antes de codear. Durante un tiempo la documentación llegaba después, y eso es tarde: si no puedes explicar la API en un ejemplo corto, la API está mal. Hoy escribiría el ejemplo de uso primero y dejaría que él dictara la firma.
También sería más agresivo con la deprecación graduada. Marcar una prop como @deprecated en TypeScript, dejarla funcionar unas versiones y avisar en cada build es infinitamente más amable que un major sorpresa. Un design system maduro no se mide por lo rápido que cambia, sino por lo predecible que es al hacerlo.