ThemeForge: un sistema de temas CSS listo para usar; un agente de IA lo instala en un solo paso.
- Categoría
- IA y LLM Local
- Publicado
- 5 octubre 2026
- Actualizado
- 6 octubre 2026
- Por
- Jacob Lloyd — escrito con ayuda de IA, después del proyecto
- Tiempo de lectura
- 35 min de lectura
En palabras simples: ThemeForge es una carpeta gratuita que contiene CSS y un pequeño script que le brinda a cualquier aplicación web un selector de temas, diez temas de colores y un conjunto completo de botones, campos, tarjetas y cuadros de diálogo ya listos para usar. Está diseñado de tal manera que un agente de codificación de IA pueda incorporarlo en una aplicación con una sola línea: el agente sigue las instrucciones de un archivo breve dentro del repositorio, y la aplicación obtiene así un selector funcional, un valor predeterminado acorde al sistema y tokens de diseño a partir de los cuales trabajar.
La mayoría de las aplicaciones web terminan teniendo tres temas, escritos en tres lugares distintos: un bloque :root en el CSS principal, un segundo bloque bajo @media (prefers-color-scheme: dark), y un tercero que algún desarrollador añadió para que el selector de temas funcionara. El resultado es siempre el mismo: un control que se ve de una manera en la página de inicio, de otra en la página de configuración, y luego surge una pregunta en el sistema de seguimiento de errores preguntando por qué el tema oscuro vuelve azul el pie de página. Escribí ThemeForge para impedir que este tipo de problemas ocurran. Se trata de una única carpeta, ui-theme/, que contiene el motor de ejecución, una capa base, las clases de componentes, los tokens de los diez temas, un selector de temas, adaptadores opcionales para Tailwind y Quasar, y un breve archivo AGENTS.md destinado a un agente de programación IA. Basta con colocar esa carpeta en el proyecto, añadir cuatro líneas al encabezado de la página, y la aplicación contará con un selector funcional y un tema predeterminado que sigue las preferencias del sistema. El agente encargado de la instalación lo hace mediante un único comando; el resto de este artículo está pensado para quienes deseen conocer su funcionamiento interno.
Este artículo sirve como guía: explica qué hay realmente en la carpeta, los diez temas y para qué sirven, la historia del contraste, así como los tokens de diseño y las clases de componentes que se detallan más adelante como referencia. La segunda mitad del texto está pensada para ser consultada fácilmente: cada token, cada componente, cada método público, para que cualquier agente de programación (o tú mismo en el futuro) pueda copiarlos tal cual.
Resumen rápido
- Descarga: github.com/LaserLloyd/ThemeForge — licencia MIT, gratis; también hay un zip con el código en la sección de Descargas.
- ¿Qué es?: una sola carpeta,
ui-theme/, que se integra en cualquier aplicación web y le aporta diez temas de colores, un selector, unas 250 variables de diseño, una capa base de elementos, un estándar tipográfico y un conjunto completo de clases de componentes (botones, campos, interruptores, tarjetas, diálogos, avisos, tablas, estructura general). Solo CSS plano y un pequeño script. No requiere compilación, ni Node, ni dependencias. - Instalación en una sola línea (tal cual aparece en el README):
python3 -c "import urllib.request as u,sys;sys.argv=['update.py','--dest','static/ui-theme'];exec(u.urlopen('https://raw.githubusercontent.com/LaserLloyd/ThemeForge/main/ui-theme/update.py').read())" - Orden del encabezado (es importante): primero
ui-theme.js(clásico, bloqueante; nuncadefernimodule), luegoui-theme-base.cssyui-components.css, después el CSS propio de la app, y finalmenteui-theme.csspara que los tokens del tema tengan prioridad. - Selector de temas: un único elemento:
<select class="ui-select" data-ui-theme-picker aria-label="Theme"></select>. Se rellena solo y permanece sincronizado entre pestañas. - Los diez temas: seis principales (Púrpura, Oro Nocturno, Glaciar, Bosque, Papel, Luz Diurna) más cuatro opcionales (Amarillo Eléctrico, LaserLloyd, LaserLloyd Claro, Rojo Noche). Todos superan la auditoría de contraste sin errores.
- Contraste: texto principal 7:1, secundario 6:1; cualquier nivel de texto en cualquier superficie alcanza 4.5:1; los bordes y estados de activación 3:1; el estado “activado” o “seleccionado” se identifica por forma y relleno, no solo por color.
- Diseñado para agentes IA: AGENTS.md es el script de instalación en formato textual; basta apuntar a él a cualquier agente para que configure el selector en un solo paso. El propio repositorio ejecuta tres tareas en GitHub Actions: clonado, auditoría de contraste,
node --checken cada archivo JS y pruebas del motor mediante Chrome sin interfaz gráfica.
¿Dónde obtenerlo?
Un único repositorio bajo licencia MIT: github.com/LaserLloyd/ThemeForge, con AGENTS.md como guía de instalación y el informe de contraste como prueba de calidad. ¿Prefieres un zip? En la sección de Descargas al final de esta página encontrarás el código completo; la página Descargas del sitio también lo ofrece. Si tu máquina no cuenta con Python, el README indica usar git clone –depth 1, npx degit o la URL de jsDelivr <script> fijada a @v1.0.0 para prototipos rápidos.
¿Qué obtienes al final?
A un paso de lograr el aspecto preferido por el usuario: un encabezado con un selector que ya conoce todos los temas, un tema oscuro y uno claro predeterminados que siguen las preferencias del sistema, y una sola línea de CSS en el código que reemplaza #1d1d39 por background: var(–surface-2). Elige un tema, recarga la página y no habrá parpadeo alguno; el script de ejecución corre sincrónicamente en <head>, lee sus propias configuraciones data-* desde la etiqueta, y establece el tema antes de que se dibuje cualquier elemento. Abre el selector en dos pestañas, cambia el tema en una y la otra se actualiza automáticamente (gracias a un listener de storage). Si imprimes con un tema oscuro u OLED, el motor alterna temporalmente a uno claro durante la impresión (o oculta la navegación según el valor de data-print-theme) para que el papel sea legible; si usas un tema claro, la página se imprime tal cual.
ui-app, ui-card, ui-btn, ui-switch y los tokens --surface-* / --text-* / --accent. El selector es un simple <select data-ui-theme-picker>; el motor lo rellena y sincroniza.La figura anterior es una demostración integrada en la página, no una captura de pantalla externa. El ejemplo completo —todos los componentes en cada tema— se encuentra en specimen/index.html del repositorio (puedes abrirlo ejecutando python3 -m http.server y accediendo a /specimen/).
Cómo se integra todo
El motor de ejecución es un pequeño script, ui-theme.js (aproximadamente 23 KB y 575 líneas) que realiza tres tareas en secuencia. Primero lee sus propios atributos data-* de la etiqueta <script> donde reside (de modo que la configuración viaje junto con el archivo), luego consulta localStorage y las preferencias del sistema para modo claro u oscuro, y finalmente escribe el tema elegido en <html data-palette> y el fondo activo en <html data-theme> (uno de amoled, dark, light) antes de que se dibuje cualquier elemento. A continuación recorre el documento en busca de elementos <select data-ui-theme-picker> y los rellena con la lista de temas disponibles (además de escuchar cambios posteriores generados por cualquier framework). Finalmente expone window.UITheme y, cuando se carga ui-components.js, también window.UIComponents; además instala un listener de storage para mantener sincronizadas las pestañas. El CSS es sencillo: ui-theme-base.css define el texto, los enlaces, el anillo de foco, las barras de desplazamiento y valores por defecto para movimientos reducidos; ui-components.css dibuja botones, campos, interruptores, tarjetas, diálogos, avisos y tablas; ui-theme.css es el único archivo que varía entre temas: define todos los tokens (color, tipografía, espaciado, radios, sombras, animaciones) para los diez temas bajo un selector [data-palette=”…”] (el tema Púrpura usa :root, es decir, sin atributo data-palette). El motor elige el tema; el CSS lo representa visualmente.
El seguimiento del tema del sistema operativo es lo que sorprende a muchos: data-default=”auto” lee prefers-color-scheme en cada carga de página; el motor combina ese valor con la elección guardada por el visitante (o con data-default-dark y data-default-light) para decidir el tema. Cambie el tema del sistema y actualice la página; esta se adaptará automáticamente. Si el usuario selecciona un tema manualmente, ese prevalecerá hasta que lo borre (o ejecute UITheme.reset()). El motor también utiliza un evento de storage para mantener sincronizadas dos pestañas abiertas de la misma aplicación, sin necesidad de comunicación con el servidor.
Instalación
El archivo AGENTS.md del repositorio contiene la guía completa; la versión breve consiste en una sola línea de comando y cuatro etiquetas en el encabezado. Desde la raíz de la aplicación:
python3 -c "import urllib.request as u,sys;sys.argv=['update.py','--dest','static/ui-theme'];exec(u.urlopen('https://raw.githubusercontent.com/LaserLloyd/ThemeForge/main/ui-theme/update.py').read())"
Este comando descarga el instalador, que a su vez obtiene la última versión, verifica cada archivo mediante la lista SHA-256 en files.json y escribe los archivos en la carpeta correspondiente. En Windows use python o py en lugar de python3; la línea funciona igual en PowerShell, cmd y bash. Las cuatro líneas que deben ir en el <head> de cada página:
<script src="/static/ui-theme/ui-theme.js"
data-themes="purple,midnight-gold,glacier,forest,paper,daylight"
data-default="auto" data-default-dark="midnight-gold" data-default-light="daylight"
data-storage-key="myapp.theme"></script>
<link rel="stylesheet" href="/static/ui-theme/ui-theme-base.css">
<link rel="stylesheet" href="/static/ui-theme/ui-components.css">
<!-- sus hojas de estilo -->
<link rel="stylesheet" href="/static/ui-theme/ui-theme.css">
Tres reglas, cada una con su razón. El script ui-theme.js es clásico y bloqueante: nunca lleva type=”module”, defer ni async, porque lee sus propios atributos desde su propia etiqueta (document.currentScript, que es null para scripts tipo módulo) y establece el tema antes del primer renderizado. Las capas base y de componentes se cargan antes que el CSS de la aplicación, de modo que las reglas propias de esta prevalezcan cuando la especificidad sea igual. ui-theme.css se carga después, evitando así que variables antiguas de la app sobrescriban los tokens del tema. El selector para elegir el tema consta de una sola línea: <select class=”ui-select” data-ui-theme-picker aria-label=”Theme”></select>. Desde el código, se pueden usar UITheme.set(‘glacier’), UITheme.current() y UITheme.onChange(fn). Según el preámbulo de AGENTS.md, la instalación completa dura unos cinco minutos.
El agente que realizó todo esto en un solo paso, en una aplicación real, actuó así: el humano le pidió a un agente de programación IA que añadiera modo oscuro y selector de temas. El agente leyó AGENTS.md, ejecutó la línea de comando en static/ui-theme/, pegó las cuatro etiquetas del encabezado en la plantilla base, sustituyó dos valores de color por var(–surface-2) y var(–text-secondary), y colocó el selector en el encabezado. Seis líneas de diferencia en el código, cero dependencias nuevas. La auditoría de contraste ya cubre todas las combinaciones posibles que generan los componentes; el motor sigue automáticamente el tema del sistema operativo; y el único riesgo que detecta la herramienta de lint es que alguien codifique un color fijo en el futuro.
Los temas
Los seis primeros son los básicos que toda aplicación ofrece por defecto; los cuatro restantes aparecen solo si la app los declara explícitamente. Todos pasan la auditoría de contraste (docs/contrast-report.md): texto principal con ratio 7:1, texto secundario con 6:1 en cualquier fondo, todos los demás niveles de texto con 4.5:1; bordes y estados on/off con 3:1; además, en cada tema el estado seleccionado o activo se reconoce por forma y color. Lista completa:
| Tema | Conjunto | Fondo | Descripción |
|---|---|---|---|
| Purple | básico | oscuro | Acento violeta y texto lavanda suave sobre índigo profundo. Es el tema base: sus valores son los predeterminados de :root. |
| Midnight Gold | básico | OLED | Dorado cálido sobre negro puro, ideal para pantallas OLED. |
| Glacier | básico | OLED | Texto y detalles azul hielo sobre negro puro, con un acento verde azulado intenso. |
| Forest | básico | OLED | Verde pálido y marrón corteza sobre negro puro. |
| Paper | básico | claro | Pergamino cálido, texto marrón tinta, tipografía serif para prosa y acento rojo. |
| Daylight | básico | claro | Blanco limpio con acento azul. |
| Electric Yellow | opcional | oscuro | Amarillo ácido sobre grafito; radios ajustados y trazos nítidos. |
| LaserLloyd | opcional | oscuro | Azul láser sobre grafito casi negro, tal como aparece en el sitio web del autor. Se combina con LaserLloyd Light. |
| LaserLloyd Light | opcional | claro | Versión clara de LaserLloyd. |
| Night Red | opcional | OLED | Tema nocturno de baja luz azul: texto rojo cálido sobre negro puro, estructura roja y cero azul en ningún color. |
Tres de los cuatro temas opcionales son llamativos por diseño. Electric Yellow es el que yo uso cuando reviso CSS en la oscuridad; quiero que la página destaque más que mi editor. LaserLloyd replica el aspecto azul y grafito de este sitio, para mantener coherencia entre él y la app que estoy desarrollando. Night Red es un “perfil nocturno” aparte; la herramienta de contraste lo analiza mediante un conjunto ampliado de 171 filas (las 147 estándar más otras para condiciones de poca luz), y en él ningún token del tema contiene componente azul (docs/contrast-report.md describe la “auditoría de cero azul”; README.md incluye la sección sobre Night Red; docs/THEMES.md también lo menciona).
Posibles problemas
-
Un color hardcodeado en el CSS es el error, no el tema. La auditoría solo verifica los tokens; un código como
color: #fffen el código de la app permanece en blanco en cualquier tema.tools/lint_colors.py(en el repositorio) y la búsqueda mediante grep en AGENTS.md §8 detectan esto; el linter en CI no ejecuta esa fase, así que ejecútelo localmente antes de hacer merge. -
ui-theme.jsdebe ser el primero en<head>, de forma clásica, no con atributosdeferomodule. Un script tipo módulo no dispone dedocument.currentScript, y el motor de ejecución lee sus propios atributosdata-*desde esa etiqueta. El síntoma es un parpadeo donde primero se muestra el tema incorrecto y luego el correcto. -
ui-theme.cssdebe cargarse después del CSS de la app, no antes. El orden correcto es: base, componentes, app, tokens. Una regla de la app con la misma especificidad que se carga después de los tokens queda anulada. -
data-themescontrola qué temas aparecen en el selector. Un tema almacenado cuyo identificador no esté endata-themesse ignora en la siguiente carga (el motor de ejecución recurre entonces al tema por defecto). Cuando elimine un tema, añada una vezdata-legacy-keyydata-legacy-mappara que el valor almacenado se migre. -
Los estilos
rounded-mdyfont-sansde Tailwind cambian tras la instalación. Eso es normal: el tema define los tokens–radius-y–font-, cuyos valores prevalecen. No intente modificarlos. -
Advertencia de hidratación en Next.js. Añada
suppressHydrationWarninga<html>y silencie la regla de linterno-sync-scriptspara el bloque head: el motor de ejecución establece atributos en¿Qué significa todo esto para mí? Lo que quería era disponer de un sistema de temas real para todas las apps que desarrollo, sin tener que crear uno desde cero cada vez; ThemeForge es la versión de ese sistema que estoy dispuesto a mantener. Las carpetas que incorporo en las apps ahora se llaman
ui-theme/y el resto depende de la propia app; los cuatro tags del bloque head y el selector de temas son idénticos en cualquier framework. El agente encargado de la instalación lee AGENTS.md, no un mensaje de Slack. La auditoría de contraste, las pruebas del motor de ejecución mediante Chrome sin interfaz gráfica y el análisisnode –checken cada archivo JS que se distribuye son los factores que me dan la confianza necesaria para seguir avanzando. Se encuentra en versión 1.0.0 porque los tokens constituyen la API pública, y el cambio de nombre de un token supone un cambio de versión mayor; cada lanzamiento se identifica mediante una etiqueta git, la carpetaVERSIONrefleja ese número y los archivos JSON se firman con la misma lista de checksums queupdate.pyvalida. La CI del propio repositorio arroja resultados positivos en Linux, Windows y Python 3.9; las mismas comprobaciones se cumplen al clonar el proyecto en esta máquina.Referencia (para agentes de IA)
El resto de este artículo constituye la guía de referencia que un agente de programación (o mi futuro yo) puede copiar tal cual. La primera parte contiene texto narrativo; la segunda se estructura para facilitar su lectura por herramientas automáticas. A continuación encontrará cada token, cada clase de componente, cada método público en
window.UIThemeywindow.UIComponents, todos los temas, cada parámetro configurable en las etiquetas script, cada adaptador, la ruta completa de instalación, el contrato de contraste y el bloque de versiones.Manifesto de archivos
El repositorio contiene dos árboles esenciales: el paquete listo para usar que la app incorpora, y los materiales de diseño y herramientas que permiten generarlo. Este artículo se centra en el paquete.
Paquete listo para usar — lo que la app copia (
ui-theme/):Archivo Tamaño Líneas Descripción ui-theme.js23,515 B 575 Motor de ejecución: aplica el tema guardado antes del primer renderizado, adapta el modo claro/oscuro según el SO mediante data-default="auto", rellena los selectores, sincroniza pestañas y exponewindow.UIThemeui-theme-base.css13,255 B — Valores por defecto de elementos: texto del cuerpo, enlaces, títulos, código, anillo de foco, barras de desplazamiento, reducción de movimiento, niveles de texto, .ui-markdown, colores de highlight.jsui-components.css51,344 B — Estilos para componentes: botones, campos, casillas, radios, interruptores, tarjetas, insignias, avisos, pestañas, controles segmentados, tablas, diálogos, menús, tooltips, notificaciones, progreso, bloques de código y estructura básica de la app ui-theme.css86,389 B (21,197 B comprimido) — Tokens de los diez temas disponibles (aprox. 20 KB tras compresión) ui-components.js5,441 B 135 Funciones opcionales con tematización: confirm(),alert(),prompt()ytoast()ui-theme.d.ts2,841 B — Declaraciones TypeScript para window.UIThemeywindow.UIComponentsupdate.py16,081 B — Instala y actualiza la carpeta; verifica cada archivo mediante SHA-256 themes.json4,089 B — Catálogo de temas (nombre, familia, base, conjunto, muestra visual, fuentes) files.json1,401 B — Lista de checksums que update.pyvalidaVERSION30 B — Versión del paquete ( ThemeForge 1.0.0 3db64d9f574f)README.md6,342 B — Instrucciones de uso específicas para el paquete adapters/quasar.css24,997 B — Mapeo para NiceGUI/Quasar adapters/quasar.js1,468 B 40 Puente JavaScript para NiceGUI/Quasar adapters/tailwind.css4,399 B — Mapeo @theme inlinede Tailwind CSS v4adapters/tailwind-v3.preset.js2,915 B 54 Preset para Tailwind CSS v3 Materiales de diseño y herramientas (no se copian en la app):
Archivo Tamaño Descripción README.md6,592 B Información general del repositorio AGENTS.md19,063 B Guía detallada para la instalación, destinada a agentes de IA CHANGELOG.md4,425 B Historial de versiones (1.0.0 es el primer lanzamiento) CLAUDE.md1,498 B Normas específicas para Claude en este repositorio CONTRIBUTING.md4,476 B Cómo colaborar en el desarrollo de los temas LICENSE1,067 B Licencia MIT; copyright © 2026. El archivo original cita al autor por su nombre personal; la marca comercial utilizada en el sitio web es distinta, y el texto que figura en disco coincide con el de la licencia original. No se incluye en el zip de descarga, ya que la línea de copyright del archivo original revela la identidad del autor, lo cual el sistema de protección de contenidos del sitio considera información sensible. docs/TOKENS.md15,391 B Descripción detallada de cada token, agrupado por categorías docs/COMPONENTS.md13,889 B Cada clase de componente, acompañada de ejemplos de código docs/THEMES.md8,081 B Información sobre los temas, su diseño y cómo crear uno nuevo docs/INTEGRATIONS.md10,604 B Instrucciones específicas para integrar el paquete en distintos frameworks docs/TEXT.md5,465 B Niveles de texto y formato Markdown predeterminado docs/UPDATING.md4,134 B Procedimientos de actualización, uso de caché y significado de los checksums docs/contrast-report.md124,372 B Resultados de la auditoría de contraste (10 temas × entre 147 y 171 filas) docs/theme-template.css9,854 B Plantilla básica para crear nuevos temas mediante tools/sync_theme.pytokens/<theme>.json~57 KB cada uno Tokens resultantes de cada tema, en formato JSON DTCG W3C examples/static-html/3,731 B index Ejemplo sencillo con HTML, CSS y JS puro examples/fastapi-jinja/2,172 B app.py Ejemplo de integración con FastAPI y Jinja specimen/index.html17,721 B Visualización local de todos los componentes en cada tema tools/check_contrast.py43,171 B Herramienta de auditoría de contraste (ejecutada en CI) tools/lint_colors.py22,711 B Detecta valores de color hardcodeados en el CSS de la app tools/sync_theme.py41,671 B Genera ui-theme.css, los archivostokens/*.jsonydocs/contrast-report.mda partir desrc/css/*.csstools/update.py16,081 B Script de instalación y verificación (equivalente a ui-theme/update.py)tools/screenshots.py3,273 B Genera capturas de pantalla de los temas para docs/images/tests/test_tools.py17,606 B Pruebas unitarias en Python (32 ejecutadas; 1 omitida en esta clonación) tests/run_browser_tests.py3,570 B Ejecuta pruebas del motor de ejecución mediante Chrome sin interfaz gráfica tests/runtime.html24,930 B Código base para las pruebas del navegador .github/workflows/ci.yml— Configuración CI: contraste, node --check, pruebas unitarias y de navegador, verificación de formato CRLF; ejecutada en Linux, Windows y Python 3.9La CI del repositorio ejecuta tres tareas:
checksen Ubuntu con Python 3.12,tools-on-windowsen Windows con Python 3.11 yupdater-on-oldest-pythonen Ubuntu con Python 3.9. La clonación local supera satisfactoriamente la validaciónnode –checken todos los archivos JavaScript distribuidos; el analizador de contraste reproduce fielmente las diez filas de datos de la auditoría, y todas las pruebas unitarias arrojan éxito (32 superadas, 1 omitida en 1.212 sen Linux con Python 3.14, fecha 05/10/2026).Tokens de diseño (grupos, función, valor de ejemplo)
Cada tema define todos los tokens. Los valores que se indican a continuación corresponden al tema Púrpura (la base
:root); los demás temas disponen de sus propios valores definidos entokens/<theme>.json(formato JSON DTCG). Para utilizarlos, emplee la sintaxisvar(--nombre). Jamás vuelva a declarar un token en el CSS de la app: asigne a sus variables un prefijo propio (--myapp-sidebar-w) o cree un alias al token (--myapp-brand: var(--accent)). Fuente:docs/TOKENS.md.Grupo Token Rol Ejemplo (púrpura) Superficies --surface-voidmás oscuro que la página (cubre todo el ancho) #08080fSuperficies --surface-0la página #0e0e1bSuperficies --surface-1navegación #14142aSuperficies --surface-2tarjetas #1d1d39Superficies --surface-3estado de hover #232342Superficies --surface-4estado activo #262648Superficies --surface-sunkenzonas hundidas #0a0a14Superficies --surface-overlaymenús, popovers #1d1d39Superficies --glass-1/2/3paneles translúcidos rgba(29,29,57,.72/.84/.92)Superficies --glass-highlightbrillo del vidrio rgba(255,255,255,.055)Superficies --code-bgfondo de código #0b0b16Texto --text-primarytexto principal #e8e8f0Texto --text-secondaryetiquetas, descripciones #a0a0bdTexto --text-tertiarypistas, marcas de tiempo #8d8db0Texto --text-disabledcontroles deshabilitados #717195Texto --text-inversetexto sobre un fondo claro #0e0e1bTexto --text-link/--text-link-hoverenlaces y texto de acento #a78bfa/#c4b5fdTexto --on-accent/--on-danger/--on-success/--on-warning/--on-media/--on-media-mutedetiqueta sobre un fondo #ffffff/#2a0808/ …Acento --accent/--accent-hover/--accent-pressedacento de marca, acciones principales #7c3aed/#8250f0/#6d28d9Acento --accent-subtle/--accent-muted/--accent-glowfondo teñido, relleno suave, brillo rgba(124,58,237,.16/.26/.35)Acento --accent-rgbpermite a JS componer rgba(var(--accent-rgb), .2)124,58,237Acento --accent-2/--accent-2-subtleacento secundario #5eead4CTA --cta/--cta-hover/--cta-pressed/--on-ctabotón de acción destacada #7c3aed/#8250f0/#6d28d9/#ffffffCTA --cta-shadow/--cta-shadow-hoversombras para el botón 0 6px 20px rgba(124,58,237,.28)/.38CTA --highlight/--highlight-subtle/--on-highlightmarcas, color de sugerencias #fcd34d/rgba(252,211,77,.12)/#251a00Líneas --border-subtle/--border/--border-stronglos bordes de los controles usan --border-strongcon una relación de 3:1#21213b/#292945/#6f6f9cLíneas --dividerlíneas finas #21213bLíneas --focus-ring/--focus-ring-width/--focus-ring-offsetanillo de enfoque #a78bfa/2px/2pxLíneas --glass-stroke/--glass-stroke-strongbordes de vidrio rgba(255,255,255,.08/.16)Estados de control --selected/--on-selectedactivado/marcado/presionado/actual según el tema Estados de control --unselected-border/--unselected-fgcontorno desactivado; relación de 3:1 en todas las superficies según el tema Estado --success/--warning/--danger/--infopuntos, barras, rellenos según el tema Estado --success-text/--warning-text/--danger-text/--info-textcolor del texto para cada estado según el tema Estado --success-subtle/--warning-subtle/--danger-subtle/--info-subtlefondo teñido para cada estado según el tema Estado --success-border/--warning-border/--danger-border/--info-borderbordes de estado según el tema Estado --idle/--idle-text/--idle-subtle/--idle-border/--idle-hoverestado neutro según el tema Estado --live/--live-text/--live-subtle/--live-border/--live-hoverindicador de “en ejecución” según el tema Código/sintaxis --syn-bg/--syn-fg/--syn-comment/--syn-keyword/--syn-string/--syn-number/--syn-function/--syn-attr/--syn-tag/--syn-builtin/--syn-type/--syn-variable/--syn-literal/--syn-operator/--syn-title/--syn-additionescala de resaltado sintáctico según el tema Código/sintaxis --code-header-bg/--code-strokemarco de bloques de código (oscuro en todos los temas) según el tema Gráficos --cat-1…--cat-12colores para series; relación de 3:1 respecto a --surface-0según el tema Gráficos --seq-1…--seq-5escala de bajo a alto según el tema Gráficos --div-1…--div-5malo, neutro, bueno según el tema Gráficos --chart-grid/--chart-axiselementos gráficos según el tema Burbujas --bubble-user-text/--bubble-assistant-text/--bubble-user-bg/--bubble-assistant-bgburbujas de chat según el tema Burbujas --rp-speech/--rp-thought/--rp-shout/--rp-whisper/--rp-ooc/--rp-action/--rp-criticalcolores para burbujas de rol según el tema Diferencias --diff-add-bg/--diff-add-text/--diff-remove-bg/--diff-remove-textvista de diferencias según el tema Tipografía --fs-xs/--fs-sm/--fs-md/--fs-lg/--fs-xl/--fs-2xltamaños de fuente según el tema Tipografía --fw-medium/--fw-semibold/--fw-boldpesos de fuente según el tema Tipografía --font-sans/--font-mono/--font-displayfamilias tipográficas según el tema Espaciado --space-1…--space-84–64 px según el tema Radio --radius-sm/--radius-md/--radius-lg/--radius-pillformas de los controles según el tema Sombras --shadow-1/--shadow-2/--shadow-3/--shadow-4niveles de sombra según el tema Capas Z --z-modal/--z-toast/--z-popover/--z-navorden de apilamiento según el tema Movimiento --motion-fast/--motion-med/--motion-slow/--easing-standardtiempos de animación (el modo “reducción de movimiento” detiene los bucles) según el tema Markdown --md-bold/--md-italic/--md-bolditalic/--md-bolditalic-glowformato Markdown en la app según el tema Misceláneos --mark-bg/--selection-text/--selection-bg/--media-filter/--quote-bar/--heartbeat-text/--heartbeat-hover/--offline-text/--on-offline/--canvas-handlecaracterísticas menores según el tema Fuente:
docs/TOKENS.md(22 grupos, ~270 tokens; el comandogrep -oE '\| \(–[a-zA-Z][a-zA-Z0-9-]*)` |’ docs/TOKENS.md | sort -u | wc -ldevuelve 272 nombres de token únicos; hay 22 grupos marcados con## `).Clases de componentes (cada modificador, una línea)
El estado se determina a partir de atributos nativos (
disabled,checked,aria-pressed,aria-selected,aria-current,aria-invalid,aria-busy,[open]); nunca se usa una clase adicional. La mayoría de los selectores constan de una sola clase, de modo que cualquier regla posterior con la misma especificidad en la aplicación prevalece. Se emplean propiedades lógicas en todo el código (funcionan correctamente en dirección derecha-izquierda). Los estados “on”, “checked”, “pressed” y “current” se representan mediante--selected; el estado “off” se indica con un contorno; los elementos deshabilitados tienen un borde discontinuo y no se rellenan. Fuente:docs/COMPONENTS.md.Clase Modificadores / partes Descripción ui-stack— Columna; el espaciado entre elementos es --space-3ui-row— Fila que se ajusta automáticamente; centrada, con separación --space-2ui-grid--ui-grid-minGenera columnas automáticamente; el valor minse establece en el elementoui-container— Centrado; ancho máximo definido por --content-maxui-spacer— Empuja el resto de los elementos de una fila hacia el final ui-app__header__nav__mainEstructura base de la app (encabezado, navegación lateral, contenido principal); la navegación se oculta por debajo de 760 px ui-brand— Título de la app en el encabezado ui-nav__label__itemNavegación lateral ui-title/ui-heading/ui-subheading/ui-lead/ui-kicker/ui-small/ui-mono/ui-num/ui-kbd/ui-code/ui-divider— Jerarquía tipográfica ( <hr>equivale a.ui-divider)ui-text-primary/-secondary/-tertiary/-disabled/-link— Clases para distintos niveles de texto (para código nuevo, se recomienda usar los tokens) ui-markdown— Contenedor para Markdown renderizado; véase docs/TEXT.mdui-btn--primary--ghost--outline--danger--cta--sm--lg--icon--blockBotones; también funcionan con <a>; se integran conaria-pressed/aria-busy/disabledui-btn-group— Botones adyacentes comparten bordes redondeados ui-field— Contenedor de campo de formulario (etiqueta + control + ayuda) ui-label— Etiqueta del campo ui-input--smCampo de texto ui-select— <select>con estilo personalizado;data-ui-theme-pickerpermite cargar el selector de temasui-textarea— <textarea>con estilo personalizadoui-help— Texto de ayuda del campo (enlaza a aria-describedby)ui-error— Mensaje de error del campo (enlaza a aria-describedby); se muestra cuandoaria-invalid="true"ui-input-group— Combinación de input + botón (p. ej. para búsquedas) ui-check— Fila con checkbox, radio o interruptor ui-checkbox— <input type="checkbox">con estilo personalizadoui-radio— <input type="radio">con estilo personalizadoui-switch— Checkbox con role="switch"que se muestra como interruptorui-switch-statedata-ondata-offEtiquetas “On”/“Off” (ocultas a lectores de pantalla; el propio interruptor anuncia su estado) ui-range— Control deslizante cuyo color se define mediante --slider-colorui-segmented— Grupo de botones que actúan como radio buttons ui-tabsui-tabLista de pestañas (las pestañas enlazadas usan <a aria-current="page">; la gestión del teclado es responsabilidad de la app)ui-card__header__title__footer--raised--interactiveTarjeta; aria-selected/aria-currentindican una tarjeta seleccionadaui-well— Zona hundida (para logs, previsualizaciones, contenido secundario) ui-stat__label__valuePar etiqueta/valor ui-badge--success--warning--danger--info--live--accent--neutralEtiqueta en forma de píldora ui-dot--success--warning--danger--info--live--neutralPunto de estado ui-count--accent--dangerChip contador ui-callout--info--success--warning--danger__titleBloque informativo coloreado (para errores, use role="alert")ui-table-wrap— Envuelve <table class="ui-table">para permitir encabezados fijos y desplazamientoui-table--hover--compacttd.numEstilos de tabla ui-dialog— <dialog>con estilo personalizadoui-drawer— Panel lateral basado en <dialog>ui-menu__itemLista de menú [data-ui-tooltip]data-ui-tooltip-side="bottom"Tooltip en cualquier elemento ui-progress— Barra de progreso lineal ui-spinner— Indicador de carga ui-skeleton— Esqueleto de carga ui-breadcrumbs— Lista de ruta de navegación ui-pagination— Paginación ui-avatar— Avatar circular ui-chip__removeChip eliminable o seleccionable ui-details— Acordeón basado en <details>ui-fieldset— Grupo de campos con leyenda ui-codeblock__bar<figure>que contiene<figcaption class="ui-codeblock__bar">y<pre>ui-empty— Estado vacío ui-bubble--user--assistantBurbuja de chat Métodos públicos en
window.UIThemeywindow.UIComponentsFuente:
ui-theme/ui-theme.d.ts(declaraciones TypeScript para ambos objetos globales).window.UITheme(UIThemeApi):Miembro Tipo Función versionstring(solo lectura)Versión de la carpeta, p. ej. "1.0.0"configobjeto (solo lectura) { themes, default, auto, storageKey, families, printTheme }current()() => stringEl slug del tema activo theme([slug])(slug?) => UIThemeInfo \| nullInformación sobre un tema (o el activo) list()() => UIThemeInfo[]Todos los temas habilitados set(slug)(slug) => stringCambia a un tema; devuelve el slug reset()() => stringVuelve al tema predeterminado; devuelve el slug partner([slug])(slug?) => string \| nullEl tema claro/oscuro correspondiente en una familia (p. ej. laserlloyd↔laserlloyd-light)toggleFamily()() => string \| nullCambia al tema complementario; devuelve el nuevo slug onChange(listener)(fn) => () => voidPermite suscribirse; devuelve una función para darse de baja. detail.printes true en cambios relacionados con la impresióntoken(name, [el])(name, element?) => stringValor crudo del token (p. ej. "#7c3aed")tokens(names, [el])(names, element?) => Record<name, string>Varios tokens a la vez color(name)(name) => stringColor resuelto en formato rgb()para el lienzocolors(names)(names) => Record<name, string>Varios colores resueltos para el lienzo mountPicker(target, [opts])(elOrSel, options?) => HTMLSelectElement \| nullRellena un select como selector; optionsson{ label, coreLabel, optInLabel, systemLabel }El evento
ui-theme-changese dispara endocumentcon{ slug, theme, previous, print }.window.UIComponents(UIComponentsApi): (requiereui-components.js)Método Firma Función confirm(opts: UIDialogOptions \| string) => Promise<boolean>confirm()con temática; las opciones son{ title, message, label, confirmLabel, cancelLabel, danger }alert(opts: UIDialogOptions \| string) => Promise<void>alert()con temáticaprompt(opts: UIPromptOptions \| string) => Promise<string \| null>prompt()con temática; devuelvenullsi se cancela. Opciones adicionales:{ value, placeholder, type }toast(message, { kind?, timeout? }) => HTMLElementNotificación con tipo info/success/warning/danger; devuelve el elementowindow.UI_THEME_MANIFEST(opcional; tiene prioridad sobre los valores por defecto):{ themes, default, defaultDark, defaultLight, storageKey, families, legacy, mirrorAttr, fontsHref, printTheme }.themesacepta un array o una cadena separada por comas.Parámetros del tag
<script>(data-*en el tag<script>)Atributo Valor por defecto Función data-themeslos seis temas principales ( purple,midnight-gold,glacier,forest,paper,daylight)Lista separada por comas de los temas habilitados; controla el selector y qué valores almacenados se respetan. Si no se define data-themes,ui-theme.jshabilita los seis temas conset==="core"(ui-theme.js:147-149).data-defaultel primer tema habilitado (el primer slug en data-themes, opurplesi no hay ninguno habilitado y no esauto)El slug del tema predeterminado, o autopara seguir la configuración del sistema operativo. La cadena literalautoactiva el seguimiento medianteprefers-color-scheme(ui-theme.js:164); si el atributo está ausente/vacío, se usa el primer tema habilitado (ui-theme.js:177-179).data-default-darkel primer tema no claro habilitado (el primer slug en data-themescuyoground !== "light", o el primero si no hay ninguno; de lo contrario,purple)La versión oscura cuando se usa auto.ui-theme.js:168-175lo selecciona cuando el sistema prefiere modo oscuro y no hay valor explícito.data-default-lightel primer tema claro habilitado (el primer slug en data-themescuyoground === "light", o el primero si no hay ninguno; de lo contrario,purple)La versión clara cuando se usa auto. Usa el mismo código (ui-theme.js:168-175).data-storage-keyui-themeClave de localStoragedonde se guarda la elección del usuario (ui-theme.js:185). AGENTS.md §3 recomienda usar<appname>.themepara evitar conflictos entre varias apps ThemeForge en el mismo origen.data-familiesfalseAgrupa los temas por familia en el selector data-print-themeautoQué tema se usa al imprimir: si el tema activo es oscuro u OLED, autocambia a la versión clara de la familia (o al primer tema claro habilitado, odaylight); un slug específico imprime ese tema;noneno hace cambios; si el tema activo es claro, se imprime tal cual (ui-theme.js:474-490).data-legacy-key— Clave de almacenamiento antigua para migrar una sola vez (con data-legacy-map)data-legacy-map— Mapa de→apara valores heredados (objeto JSON)data-mirror-attr— Copia el tema activo a otro atributo (para librerías que lo lean) data-fonts-href— URL de una hoja de estilo con las fuentes del tema Fuente:
ui-theme.js:147-149, 164, 168-174, 185, 474-490(valores por defecto en tiempo de ejecución) y AGENTS.md §3, §12 (sobrescrituras recomendadas; p. ej. definirdata-themescon los seis temas principales,data-default="auto",data-storage-key="<appname>.theme").Adaptadores
Adaptador Archivos Función NiceGUI / Quasar adapters/quasar.js+adapters/quasar.cssCargue adapters/quasar.jsjusto después deui-theme.jsyadapters/quasar.csstrasui-theme.css(medianteui.add_head_html). Los toggles, checkboxes y radios de Quasar usan el diseño de estado de control; los controles deshabilitados se muestran con trazos y opacidad total. Elimine cualquier llamada aui.dark_mode(); el adaptador lo gestiona.Tailwind CSS v4 adapters/tailwind.css@import "./<path>/ui-theme/adapters/tailwind.css";después de@import "tailwindcss";. Exponebg-surface-2,text-fg,text-fg-muted,bg-accent,text-on-accent,border-line, etc. El texto usafg-*porque los tamañostext-*pertenecen a Tailwind.Tailwind CSS v3 adapters/tailwind-v3.preset.jspresets: [require('./<path>/ui-theme/adapters/tailwind-v3.preset.js')]. Contiene los mismos tokens que el adaptador v4, expresados como preset v3.El adaptador de Tailwind mapea los tokens del tema; el de Quasar mapea las clases de componentes. Ninguno es obligatorio para que el resto del paquete funcione.
Ruta completa de instalación (texto literal, sección de instalación de
AGENTS.md)AGENTS.mdes el script de instalación en formato de texto, pensado para agentes de codificación IA. A continuación se transcriben íntegramente las dos primeras secciones; este es el prompt al que se apunta a un agente IA. Los caracteres se escapan según la regla §8 de Article-Forge (un<blockquote>crudo ejecuta markdown literalmente, por lo que<,>,&se escapan como<,>,&).## 1. Decida una vez | Pregunta | Respuesta por defecto | |---|---| | ¿Dónde va la carpeta? | En la carpeta estática de la app, servida tal cual: `static/ui-theme/` (Flask, FastAPI, Django), `public/ui-theme/` (Vite, Next.js, Create React App), `app/static/ui-theme/` o junto a `index.html` en sitios simples. | | ¿Qué temas? | Los seis temas principales: `purple,midnight-gold,glacier,forest,paper,daylight`. Añada algún tema optativo (`night-red`, `electric-yellow`, `laserlloyd`, `laserlloyd-light`) solo si el usuario lo solicita. | | ¿Tema predeterminado? | `auto`: sigue la configuración de claro/oscuro del sistema hasta que el usuario elija uno (`data-default-dark="midnight-gold"`, `data-default-light="daylight"`). | | ¿Clave de almacenamiento? | `<appname>.theme`, p. ej. `notes.theme`. | ## 2. Instale Ejecute desde la carpeta raíz de la app (Python 3.9 o superior; cambie `static/ui-theme` por la carpeta elegida):
En Windows use `python` (o `py`) en vez de `python3`; la línea es idéntica en PowerShell, cmd y bash. Descarga el instalador, que obtiene la última versión desde GitHub, verifica cada archivo contra su lista de checksums y crea la carpeta. Para actualizaciones posteriores: `python3 static/ui-theme/update.py`. Sin conexión: descargue un archivo comprimido (`.tar.gz`) en otro lugar y ejecute `python3 update.py --source <archive> --dest static/ui-theme` usando el `update.py` de la carpeta `ui-theme/`. Otras formas, si Python no está disponible: - **git:** `git clone --depth 1 https://github.com/LaserLloyd/ThemeForge.git ut-tmp`, copie `ut-tmp/ui-theme` a la app y elimine `ut-tmp`. - **Node:** `npx degit LaserLloyd/ThemeForge/ui-theme#v1.0.0 static/ui-theme`. - **Sin instalación (prototipos, archivos HTML simples):** cargue los archivos desde jsDelivr, fijados a una versión: `https://cdn.jsdelivr.net/gh/LaserLloyd/ThemeForge@v1.0.0/ui-theme/ui-theme.js` (y la misma ruta para cada archivo CSS). Nunca use `@main`; el CDN lo cachea hasta 12 horas. Para actualizar una página CDN, cambie la versión fijada; los pasos 8.5, 9 y 10 no se aplican a ella.python3 -c "import urllib.request as u,sys;sys.argv=['update.py','--dest','static/ui-theme'];exec(u.urlopen('https://raw.githubusercontent.com/LaserLloyd/ThemeForge/main/ui-theme/update.py').read())"El resto de
AGENTS.md(secciones 3–12) trata el bloque head, el selector, cómo generar desde tokens, reglas, gráficos, verificación, la línea que debe incluirse en el propioAGENTS.mdde la app para mantener la convención entre agentes, el comando de actualización, la tabla de solución de problemas y la lista de referencia anterior.Auditoría de contraste (resumen, todos los temas)
tools/check_contrast.pyse ejecuta en CI con cada push. El informe completo ocupa 124 KB (una sección por tema más un resumen) endocs/contrast-report.md; el resumen es:Tema Conjunto Perfil Comprobaciones Fallos purple core estándar 147 0 midnight-gold core estándar 147 0 glacier core estándar 147 0 forest core estándar 147 0 paper core estándar 147 0 daylight core estándar 147 0 electric-yellow opt-in estándar 147 0 laserlloyd opt-in estándar 147 0 laserlloyd-light opt-in estándar 147 0 night-red opt-in noche 171 0 El contrato que todo tema debe cumplir: texto principal 7:1, texto secundario 6:1 (el informe de contraste señala “la promesa de 6:1 del contrato” —
docs/contrast-report.mdmarca cada filatext-secondary on surface-2con esa frase exacta), cualquier nivel de texto sobre cualquier superficie 4.5:1, deshabilitado 3:1, sobre-accent y sobre-estado 4.5:1, texto de estado sobre su fondo teñido 4.5:1, sobre-seleccionado 4.5:1, seleccionado / borde-no-seleccionado / fg-no-seleccionado en cualquier superficie 3:1, borde-fuerte / pista-de-control / anillo-de-enfoque 3:1, colores de sintaxis sobre--syn-bg4.5:1 (los comentarios 3.5:1). Activado/desactivado por forma: un control “activado” se rellena con--selected; uno “desactivado” es un contorno en--unselected-border; en un interruptor, el estado “on” añade una barra al botón y “off” un anillo. Deshabilitado aparece como trazo sin relleno en el nivel deshabilitado, incluso si está activado.Frameworks
Según
docs/INTEGRATIONS.md: HTML plano · FastAPI, Flask, Starlette (plantillas Jinja) · Django · Vite, Vue, Svelte, SvelteKit, Astro · Next.js (router de app) · NiceGUI (Quasar) · Tailwind CSS · Electron, Tauri, pywebview · Fuentes · Gráficos · Política de seguridad de contenidos. Por framework: coloqueui-theme/donde el framework sirva archivos estáticos, añada el bloque head en el orden indicado en AGENTS.md §3 y sirva la carpeta sin cachear (o versione las URLs) para que las actualizaciones se reflejen. El repositorio incluye dos ejemplos concretos:examples/static-html/(una página única) yexamples/fastapi-jinja/(FastAPI + Jinja, con unapp.pyde 2,172 bytes).Impresión, contraste y animación
Tres elementos que el motor detecta y el CSS responde sin necesidad de banderas temáticas:
-
Impresión —
data-print-themecontrola qué tema se imprime (ui-theme.js:474-490): cuando el tema activo es oscuro u OLED,autocambia automáticamente al tema claro correspondiente (o al primer tema claro habilitado, o adaylight); si se indica un nombre específico, se imprime ese tema;noneevita cualquier cambio. Cuando el tema activo es claro, la página se imprime tal como está. La función de impresión oculta la barra lateral de navegación, los mensajes emergentes, menús, cajones y tooltips. -
prefers-contrast: more— todos los temas aumentan el grosor de las líneas hasta alcanzar el nivel máximo permitido, y elevan el texto terciario al nivel secundario. -
prefers-reduced-motion— detiene los bucles animados y acorta la duración de las animaciones; los tokens--motion-*pasan a tener valor 0.
Versiones
Según el repositorio en la versión actual descrita en este artículo (clonado el 05-10-2026):
$ git log -1 --format='%h %ci' 56fb665 2026-10-05 21:46:52 +0900 # ThemeForge 1.0.0 $ cat ui-theme/VERSION ThemeForge 1.0.0 3db64d9f574f $ wc -c ui-theme/*.js ui-theme/*.css ui-theme/*.ts ui-theme/*.py 2>/dev/null 23515 ui-theme.js 5441 ui-components.js 2841 ui-theme.d.ts 16081 update.py 13255 ui-theme-base.css 51344 ui-components.css 86389 ui-theme.css $ node --check ui-theme.js ui-components.js (node v24.18.0) — Todo correcto en ambos archivos, así como en adapters/quasar.js y adapters/tailwind-v3.preset.js $ python -m unittest discover -s tests Se ejecutaron 32 pruebas en 1.212 segundos. OK (omitidas=1) $ python tools/check_contrast.py --fail-on any … ## Resumen | Tema | Conjunto | Perfil | Pruebas | Fallos | | purple | core | standard | 147 | 0 | | midnight-gold | core | standard | 147 | 0 | | glacier | core | standard | 147 | 0 | | forest | core | standard | 147 | 0 | | paper | core | standard | 147 | 0 | | daylight | core | standard | 147 | 0 | | electric-yellow | opt-in | standard | 147 | 0 | | laserlloyd | opt-in | standard | 147 | 0 | | laserlloyd-light | opt-in | standard | 147 | 0 | | night-red | opt-in | night | 171 | 0 |Compatibilidad: Chrome, Edge, Firefox y Safari actuales (2024 o posteriores). Esta capa utiliza
:has(),color-mix()y la API de popovers; los navegadores antiguos mantienen los colores, pero algunos estados de componentes y menús podrían mostrarse de forma degradada.Relacionado: ChatForge: un asistente de IA local basado en NPU para la Copilot Key · StudioForge: un servidor LLM exclusivo para GPU · DisPatch: chat de IA autoalojado · Mi pila de agentes de IA local · Cómo hacer que una IA adapte cualquier proyecto a su sistema
Descargas
Gratis para uso personal. Si te ahorra una tarde, el botón del café está aquí cerca.
-