Volver a inicio
Vista previa de Prelude DS Icons

Crear el icono en Figma es solo el inicio

Prelude DS Icons. De un catálogo de iconos de 10,9 KB sin dependencias a un monorepo pnpm que genera cuatro paquetes nativos por framework desde un único set de SVGs.

Design EngineerProyecto personal2026

Una librería que nadie encuentra no es una librería, es una carpeta

Llevo tiempo construyendo la iconografía de un design system que corre en producción, y ahí aprendí dónde está el cuello de botella real. No está en el dibujo. Está en todo lo que pasa después.

Un icono existe de verdad cuando alguien que no lo dibujó puede encontrarlo por nombre o por categoría, verlo al tamaño exacto al que va a usarlo, inspeccionar su código, copiarlo y meterlo en su pantalla sin abrir Figma, sin instalar nada y sin preguntarle a la persona de diseño. Hasta ese momento, la librería es un directorio de ficheros con un nombre bonito.

Las opciones que había en el mercado fallaban siempre por el mismo sitio. O venían atadas a un framework, o llegaban dentro de una librería de UI completa que arrastra su propia identidad visual, o eran galerías bonitas sin inspección del SVG ni exportación real. Ninguna resolvía el problema de distribución, que es el único que importa cuando el equipo que consume los iconos no es el que los produce. Y una galería sola tampoco lo resuelve, porque el equipo que construye en React necesita componentes React, el de Angular necesita componentes Angular, y el que solo quiere un custom element necesita eso.

Prelude nace de ahí. Empezó como la infraestructura de distribución de un set de iconos. Después, la infraestructura aprendió a hablar todos los frameworks.

Cero dependencias no es una restricción técnica, es la tesis

La decisión que ordena todo el proyecto es que un catálogo de iconos es infraestructura, y la infraestructura tiene que sobrevivir a las decisiones de framework del equipo que la usa.

Si el catálogo necesita React para abrirse, muere el día que el equipo migra. Si necesita un npm install de 40 KB de runtime para pintar una rejilla de SVGs y abrir un modal, está pagando un peaje por algo que document.createElement y <dialog> resuelven de forma nativa. Toda la lógica de la aplicación cabe en 255 líneas de JavaScript, y esa cifra no es una anécdota de optimización: es la prueba de que el problema no requería un framework.

El resultado son 10,9 KB de JS y CSS minificados para el catálogo entero, y cero dependencias de producción. Vite se queda en la capa de desarrollo, donde aporta HMR y un build con assets hasheados, sin filtrarse a lo que descarga el usuario final.

Soltar un fichero es toda la API

El registro de iconos no se mantiene a mano. Un script de Node recorre /public/icons/, extrae nombre, categoría, etiquetas y tamaños de cada SVG, y genera icons.json. En desarrollo, chokidar vigila la carpeta y regenera el manifiesto al vuelo. La razón de fondo es la misma que aplico en cualquier design system: cualquier proceso que obligue a una persona a registrar el mismo dato en dos sitios acaba desincronizado. Siempre. El manifiesto es un dato derivado, no un dato mantenido. Añadir un icono al catálogo es una acción de un solo paso, y esa es la condición para que la librería crezca sin que yo sea el intermediario.

Normalizar en el consumo, no en el origen

Cada SVG se procesa justo antes de renderizarse: se le quitan los atributos width y height para que escale con el contexto, y se le inyectan aria-hidden="true" y focusable="false" para que no se anuncie a lectores de pantalla ni entre en el orden de tabulación. Lo importante es dónde ocurre eso. La normalización pasa en tiempo de carga y los ficheros fuente quedan intactos. El SVG del repositorio sigue siendo el artefacto de diseño puro, exportable a una fuente de iconos, a un paquete o de vuelta a Figma sin arrastrar decisiones tomadas para una web concreta. Es la diferencia entre una fuente de la verdad y una fuente ya contaminada por su primer consumidor.

Tokens también aquí

Espaciado, tipografía, color, radios, sombras y desenfoques viven como custom properties en :root. Los componentes consumen tokens, nunca valores literales. Cambiar el tema o añadir modo oscuro es sobrescribir propiedades, no reescribir hojas de estilo. Es exactamente el mismo principio que aplico en un design system de producto, aplicado a una herramienta de 794 líneas de CSS, porque la escala no cambia el argumento.

Los detalles que no se ven en una captura

La ficha de cada icono se abre en un <dialog> nativo, que ya trae comportamiento modal, backdrop y cierre con Escape sin librería de por medio. Encima de eso, el foco entra en el modal al abrirse y vuelve al elemento que lo lanzó al cerrarse, que es la parte que casi ninguna implementación casera resuelve.

El resaltado de sintaxis del código SVG tampoco usa librería. Es un tokenizador basado en expresiones regulares que envuelve etiquetas, atributos y valores en <span> con clases CSS. Ahorra un paquete entero de highlighting para el único lenguaje que esta aplicación necesita mostrar.

El botón de copiar usa la Clipboard API con fallback a execCommand para navegadores antiguos, porque una herramienta de design system la acaba abriendo alguien en el navegador corporativo que le han puesto, no en el que tú tienes.

Y todas las transiciones respetan prefers-reduced-motion. La accesibilidad aquí no es una auditoría que pasé al final: es HTML semántico (<dialog>, <nav>, <aside>, <section>), etiquetas ARIA en todo lo interactivo, navegación completa por teclado y skip link, decidido en el mismo momento en que se escribió cada componente.

De galería a monorepo: una fuente, cuatro frameworks

La galería demostró la tesis: un catálogo sin dependencias puede distribuir iconos sin imponer un framework. La pregunta siguiente era inevitable. ¿Y si los equipos que sí usan un framework pudieran consumir los mismos iconos como componentes nativos, sin adaptar nada?

La respuesta es un monorepo pnpm con un motor de generación de código en su centro. Un script TypeScript lee los SVGs fuente, los optimiza con SVGO usando plugins propios, y escribe cuatro paquetes completos: SVG crudo con metadatos TypeScript, Web Components con Shadow DOM, componentes React con forwardRef y context provider, y componentes Angular standalone con inyección de dependencias.

La galería sigue existiendo dentro del monorepo y sigue pesando 10,9 KB. Pero ahora es un consumidor entre cinco. Los mismos SVGs fuente que alimentan la galería alimentan también cada paquete de framework, y añadir un icono nuevo a los cinco outputs sigue siendo un paso: soltar el fichero.

Un script escribe cuatro paquetes

generate-icons.mts es el corazón del sistema. Lee cada SVG de la carpeta fuente, lo pasa por un pipeline SVGO que elimina dimensiones, convierte fills a currentColor y normaliza atributos. Después genera un archivo TypeScript por icono en cada paquete: un export de metadatos para @prelude-icons/svg, una clase Custom Element con Shadow DOM para /web, un componente forwardRef para /react, y un componente standalone con DomSanitizer para /angular. Cada convención de nombres, cada atributo de accesibilidad y cada forma de exportación se decide en un solo sitio. Si la API cambia, cambia un archivo.

Cada framework recibe una API nativa

El paquete React te da CalendarIcon como componente SVG con forwardRef, o un PiIcon genérico con un IconProvider por contexto para renderizado dinámico. El paquete Angular te da componentes standalone como CalendarIconComponent con bindings @Input y DomSanitizer, más un PiIconComponent con token de inyección para el registro. El paquete Web Components te da pi-icon-calendar como Custom Element con atributos observados para size, color y label. El consumidor no se adapta a la librería. La librería se adapta a él.

La galería es ahora un consumidor entre cinco

La galería vanilla que inició el proyecto no ha cambiado. Sigue corriendo con cero dependencias de producción, sigue pesando 10,9 KB, y sigue actualizándose al soltar un fichero. La diferencia es que el mismo SVG fuente que alimenta la galería ahora también alimenta cuatro paquetes npm con doble exportación ESM y CJS, declaraciones TypeScript e imports tree-shakeable. La galería no fue reemplazada. Le llegó compañía.

Impacto medible

10,9 KB

peso total del catálogo en producción, JS y CSS minificados

0

dependencias de producción, con 4 solo en desarrollo

4

paquetes por framework generados desde un único set de SVGs fuente

1 script

genera los cuatro paquetes: SVG, Web Components, React y Angular

Lo que queda

La galería está entregada. El monorepo y los cuatro paquetes están entregados. La cobertura de iconos sigue creciendo por categorías y cada alta hereda gratis la optimización, el codegen multi-framework y la accesibilidad. Lo que falta es apretar los dos extremos del pipeline que todavía son manuales.

Primero, control de calidad vectorial automatizado en CI: comprobar que cada icono respeta la rejilla, el grosor de trazo y el tamaño óptico antes de entrar al catálogo, para que la consistencia deje de depender de mi ojo. Después, conexión con las variables de Figma, de modo que el nombre que se decide en diseño sea el mismo que llega al código, sin traducción manual.

El objetivo no es tener más iconos. Es que añadir el icono número doscientos cueste exactamente lo mismo que añadir el número seis.