--- title: Eidos Theming — Comparación + decisiones type: notes audience: human + agent status: current source: extracted from src/uix/eidos/THEMING.md (was §15 + §17) --- # Eidos Theming — Comparación + decisiones > Material E3 del theming: cómo se compara eidos con las librerías de referencia, > y el FAQ de las decisiones polémicas (el porqué de las elecciones). Extraído de > [`THEMING.md`](./THEMING.md) (referencia E1). --- ## Comparación con librerías de referencia ### Bundle size (app típica con 10 componentes) | Sistema | Raw | Gzip | Strategy | |---|---|---|---| | Tailwind v4 | ~30 KB | ~10 KB | JIT atomic classes | | **Eidos + purge** | **116 KB** | **13.6 KB** | **JIT custom-property purge** | | Chakra Panda v3 | ~30 KB | ~12 KB | Build-time JIT | | Mantine | ~60 KB | ~18 KB | Sin purge | | Radix Themes | ~80 KB | ~22 KB | Sin purge | | shadcn/ui | varía | varía | Copy-paste, no centralized | | **Eidos sin purge** | **218 KB** | **25 KB** | Single CSS | ### Theming features | Feature | Eidos | Radix Themes | Chakra Panda | Mantine | shadcn | Tailwind v4 | |---|---|---|---|---|---|---| | **Token scope as data** | ✅ TSC | ❌ implícito | ⚠️ build-time | ❌ runtime | ❌ N/A | ❌ N/A | | **Auto-inferencia de deps** | ✅ `var()` parse | ❌ | ✅ types | ❌ | N/A | N/A | | **Cross-axis collision** | ✅ explícito | ❌ | ⚠️ partial | ❌ | N/A | N/A | | **Composite scopes** | ✅ `[axis:v, …]` | ❌ | ✅ conditional pairs | ❌ | N/A | N/A | | **9 color roles canónicos** | ✅ libro | ⚠️ 6 accents | ❌ open | ❌ open | ⚠️ 4 roles | ❌ open | | **6 size canon coordinated** | ✅ | ⚠️ 1-3 | ⚠️ 5 | ⚠️ 5 | ❌ | N/A | | **Density runtime** | ✅ 3 levels | ❌ | ❌ | ⚠️ partial | ❌ | ❌ | | **Contract introspection** | ✅ typed | ⚠️ docs | ✅ Panda | ⚠️ docs | ❌ | ❌ | | **Runtime override** | ✅ contract-aware | ⚠️ via CSS vars | ❌ | ✅ CSSVarsProvider | ⚠️ via CSS | ❌ | | **Persistence versioned** | ✅ envelope | ❌ | ❌ | ❌ | ❌ | N/A | | **Perceptual layer (sema)** | ✅ unique | ❌ | ❌ | ❌ | ❌ | ❌ | ### Mental model | Sistema | Token philosophy | |---|---| | Eidos | 7 layers de indirección, scope-as-contract, perceptual integration | | Radix Themes | 3 layers, accent runtime swap, no scope contract | | Chakra Panda | Conditional values build-time, recipe system | | Mantine | Theme provider runtime, string interpolation | | shadcn | Plano `--primary` + `.dark`, copy-paste components | | Tailwind v4 | `@theme` directive, atomic utilities, no tokens compuestos | **Lectura crítica**: Eidos NO es más simple que Tailwind ni más ergonómico que shadcn. Es **más expresivo** en la dimensión "qué puede comunicar un componente". Si tu app solo necesita un color primario y un dark mode, shadcn es la respuesta. Si tu app necesita diferenciar perceptualmente entre "guardar borrador" (affirm) y "borrar permanentemente" (loss) con tokens y animaciones distintas, Eidos es el sistema. --- ## FAQ — decisiones polémicas ### ¿Por qué el theming no vive en Morfo? ¿No debería Morfo ser source of truth de todo? Pregunta CRÍTICA — respuesta detallada en [sección 1.bis](#1bis-theming-vive-en-eidos-no-en-morfo-por-diseño). Resumen: morfo es source-of-truth del **contrato cross-layer** (parts, events, attrs, archetypes, attr values). Tokens/themes/recipes pertenecen a Eidos por diseño explícito de la arquitectura (`active_architecture.md` §9). La regla 2-de-3 lo deriva: los tokens visuales los consume solo eidos → 1-de-3 → no entra en morfo. La integración eidos↔morfo se hace **a través del DOM** (los recipes targetean attrs que morfo declara), NO importando objetos morfo en TS (la regla #6 lo prohíbe explícitamente). ### ¿Por qué 7 capas de indirección? Parece excesivo Cada capa sirve un override point real: - Sin capa 1, no puedes traer una paleta Radix custom. - Sin capa 2, no puedes remapear roles. - Sin capa 3, no puedes ajustar slots per role. - Sin capa 4, no puedes overridear un color SOLO para un componente. - Sin capa 5, no puedes tener palette dinámico por instancia. - Sin capa 6, no puedes combinar variant × palette. - Sin capa 7, los recipes mezclan tokens externos con internos. La capa 4 es la más sospechosa de redundancia. Es candidata a deprecate si después de 6 meses ningún consumer la usa. ### ¿Por qué TSC y no simplemente convención? **Convención falla en silencio**. El bug del Toggle (pre-TSC) habría quedado escondido años. Con TSC, el build falla. Es la diferencia entre "deberías hacerlo bien" y "no puedes hacerlo mal". ### ¿Por qué no usar Tailwind si es más pequeño? Tailwind: - No tiene roles semánticos canónicos (success/danger/warning sí pero son del mundo Bootstrap). - No tiene perceptual layer (sema). - No tiene density runtime. - No tiene contract introspection programática. Pero si tu app es simple, **úsalo**. Eidos justifica su complejidad sólo cuando la app necesita las dimensiones que Eidos cubre. ### ¿Por qué no atomic classes como Tailwind? Custom properties permiten: - **Cascada dinámica** (palette overrides en runtime). - **Theme switching** sin recompile. - **Persistencia** del user's theme. - **Composability** con sema (`event:*` scope). Atomic classes son más comprimibles pero son estáticas. Imposible hacer `--palette-solid` cambie con `data-color='affirm'` desde atomic classes sin generar ×N variantes en build. ### ¿Por qué inventar "TSC" y no usar @scope nativo de CSS? `@scope` (CSS Cascading Modules L6) es bleeding-edge: Chrome 118+, Firefox 128+, Safari aún no. No es production-ready 2026. Cuando @scope sea universal, TSC podría re-implementarse encima de él. La superficie del config (declarations[] + scope) seguiría igual; sólo cambiaría el CSS emitido. ### ¿Por qué `event:` en TSC y no usar el `data-motion-ref` del doc motion? Tres razones: 1. **TSC ya existe y funciona**. `data-motion-ref` requeriría un atributo DOM nuevo, runtime para inyectarlo, registry separado. 2. **Sema ya emite `data-event`**. Reutilizar es 0 coste arquitectónico. 3. **Composable**: `scope: ['event:announce', 'color:affirm']` permite diferenciar la animación según valencia. `data-motion-ref` perdería eso o requeriría keys más complejas. ### ¿Cuál es el siguiente paso? Pendientes deferred: - Migrar los 5 palette consumers (button, checkbox, switch, radio-group, toggle-group) a `declarations[]` para uniformidad. - Implementar el primer caso real de `scope: 'event:*'` (e.g. toast bg-during-announce). - Decidir si colapsar la capa 4 (component-color) — defer hasta que un consumer pida ese punto de extensión. ---