5.9 KiB
Tema · Untitled UI · Carbon
Demo de theming en vivo para activeUIX sobre el sistema de Eidos. Es a la vez
un tema completo (untitled-ui-light / untitled-ui-dark) y un playground
que muestra todos los elementos del tema: roles de color, rampas físicas de 12
pasos, superficies, tipografía, espaciado, radios, sombras, tamaños y el aspecto
real de cada control con sus variantes — más la capa semántica (sema).
Ruta: /temas/untitled-ui.
Identidad del tema
Fiel a untitledui.com (tipografía Inter, sombras en
capas, radios de 8px, anillos de foco suaves) pero con una decisión deliberada:
el primario es carbón (grafito casi negro #1F2024), monocromo al estilo
Vercel / Linear — no el violeta de marca de UUI. El violeta de UUI (#7F56D9)
se reubica como secundario.
Los 9 roles canónicos del framework quedan retintados con jerarquía diferenciada (ninguno se repite):
| Rol | Escala | Color | Papel |
|---|---|---|---|
primary |
carbon | #1F2024 |
Acción principal, monocroma. Texto auto-invertido vía slots.contrast: '1'. |
secondary |
violet | #7F56D9 |
Violeta de marca UUI. |
tertiary |
blue | #1570EF |
Acento azul. |
neutral |
gray | #667085 |
Gris frío UUI. |
affirm |
teal | — | Positivo ligero (baja activación). |
fulfill |
green | — | Éxito / completado. |
risk |
amber | #DC6803 |
Aviso. |
threat |
red | #D92D20 |
Error / peligro. |
loss |
plum | #7A3AAD |
Pérdida grave: violeta sombrío, posterior (más oscuro que el secundario). |
Otros tokens del tema:
- Tipografía: Inter (
primary/secondary/display), Roboto Mono (mono). Letter-spacing negativo en los displays (hero/h1/h2a-0.02em,h3a-0.01em). - Radios: escala con
md = 8pxcomo base UUI (none→full). - Foco:
focusRingconoffset 1px+width 3px; el halo lavanda se pinta enuntitled-ui.css(--uui-ring, derivado de--color-secondary-solid). - Sombras: set en capas estilo UUI (light + dark).
- Densidad:
compact/comfortable/spacious— funcional, escala el espaciado y la altura de los controles (ver «Correcciones del engine» abajo).
El tema vive en _lib/untitled-ui.ts como un EidosConfig independiente
(no parchea el base): define escalas light/dark, el ColorRoleMap y los dos
themes. No inventa variants ni roles nuevos — solo retinta lo canónico.
Componentes mostrados
Foundations (markup propio sobre tokens del tema): roles + rampas + superficies, tipografía, espaciado / radio / sombra / tamaño.
Controles eidos reales, todos dirigidos por el tema:
- Button (6 variants × roles × tamaños × estados)
- Badge
- Form: Field, Select, Checkbox, RadioGroup, Switch, Slider
- Feedback: Banner, Progress, Avatar, Card
- Tabs, Accordion
- Toggle, ToggleGroup (single + multiple, segmentado)
- Toolbar
- Overlays: Popover, Dialog (portaleados)
- Menús: Menubar, DropdownMenu, ContextMenu, Tooltip (portaleados)
- Semántica: la capa sema (sonido + haptic + reacción visual
data-event-*) encendida en el layout víacreateActiveUix({ events: { sound, haptic, components } }).
Estructura
| Archivo | Rol |
|---|---|
_lib/untitled-ui.ts |
Config del tema (EidosConfig): escalas, roles, themes light/dark. Exporta THEME_ROLES, THEME_SCALES, COLOR_STEPS. |
+layout@.svelte |
Raíz de composición. Arranca ActiveUix + Soma + ActiveEidos con el tema. Posee modo (claro/oscuro) y densidad como fuentes visuales. El @ resetea la herencia del chrome de /uix. |
+page.svelte |
La página demo. |
theme.css |
Chrome del shell + helpers de layout ([data-temas-*]). |
untitled-ui.css |
Capa de pulido scoped a [data-theme^='untitled-ui']: anillo lavanda, sombras de control, bordes. |
Correcciones del engine de theming (dependencias)
Este tema fue el primer consumidor que reveló dos bugs del engine de Eidos,
corregidos a nivel engine (sin deuda en el tema). Documentados en detalle en
src/uix/eidos/THEMING.md §20:
- Densidad inerte —
data-densityno afectaba a nada. Fix:renderStaticCssemitespaceycontrol-heightcomocalc(value * var(--density-*-scale)), de modo que[data-density]reescala de verdad. (§20.1) contrastilegible sobre sólidos — el texto sobre el sólido de color resolvía al paso 12 de la escala (casi invisible). Fix: el slotcontrastpor defecto resuelve avar(--color-content-on-solid, …). Por eso el primario carbón lleva texto casi blanco sin configurar nada extra. (§20.2)
Hallazgo de autoría: bind:value debe respetar el default $bindable
La página tuvo un síntoma engañoso: bajo SSR renderizaba completa (~1.1 MB) pero
la hidratación en cliente desmontaba casi todo el árbol (los componentes
quedaban como anclas vacías <!---->), sin error visible en consola. Parecía un
fallo de hidratación de los componentes con portal.
La causa real era un único bind:value={menubarValue} con
menubarValue = $state(undefined), mientras <Menubar> declara
value = $bindable(''). Svelte 5 lanza props_invalid_value («Cannot do
bind:value={undefined} when value has a fallback value») durante la
hidratación; el error aborta el render de la página y desmonta el árbol.
Lección: al usar bind: contra un prop con default $bindable, el estado local
debe inicializarse con un valor del mismo tipo que el default — nunca undefined.
Aquí: menubarValue = $state('') y align = $state<string[]>(['left'])
(ToggleGroup usa value = $bindable([]), también para type="single").
No era un bug del engine de theming ni de los portales: los componentes con portal
(Popover / Dialog / Menubar / DropdownMenu / Tooltip) renderizan, hidratan y abren
correctamente cuando el bind:value es válido.