Ir al contenido principal

Devlog · Fase 0 · 6/7

Verre, el design system de Aubia

Verre sustituye el antiguo diseño de aubia.dev y se usará también en aubia.cloud y en la aplicación de escritorio: dos temas en grises neutros, un cristal gris, Geist y componentes de un registry.

Publicado el Actualizado el 10 min de lectura

  • design-system
  • verre
  • tailwind
  • motion

El fondo oscuro de aubia.dev es un gris casi negro, #0E0F12, sin el tono azulado que tenía cuando se publicó este artículo por primera vez. El tema claro, «alba», parte de un gris muy pálido, #EDEFF2. Ambos proceden de Verre, la nueva identidad visual de Aubia, cuya maqueta aprobé el 5 de octubre de 2026.

En agosto, este artículo describía otro sitio: dos temas Catppuccin completos, un cristal que dejaba ver las manchas de color del fondo y Chakra Petch, una fuente angulosa reservada a los títulos de aubia.dev. Cada aplicación de Aubia definía sus propios colores y sus propios componentes, y la aplicación de escritorio preveía cuatro variantes de tema, mientras que el sitio ofrecía dos. Verre da a las tres aplicaciones una sola identidad, y el sitio es el primero en aplicarla.

De Luma, el estilo de componentes de shadcn/ui, tomé los radios de borde, el espaciado, la elevación y las animaciones de estado. El material, los colores, la tipografía y los iconos son propios de Verre.

Un shadcn registry proporciona los componentes

Los componentes de Verre se encuentran en un shadcn registry privado, servido desde GitHub. Una aplicación no los instala como dependencia: el comando shadcn add copia su código fuente en el repositorio de la aplicación, junto al resto de su código. Descarté un paquete npm versionado: habría entregado código compilado en lugar de esas fuentes, y yo habría tenido que publicar y proteger otro registry privado de npm.

Instalé 91 elementos del registry en el sitio. Un manifiesto guarda el hash SHA-256 de cada archivo copiado, y un test comprueba que la copia sigue siendo idéntica al original. Para que ese control se sostenga, una copia nunca se modifica directamente: un ajuste de estilo pasa por una hoja de estilo aparte, importada después de las del registry.

Estos componentes se construyen sobre Base UI 1.9.0, que shadcn recomienda ahora en lugar de Radix. Base UI gestiona el foco, la navegación con el teclado y los roles ARIA. Un enlace con aspecto de botón pasa por un componente ButtonLink aparte, porque la primitiva Button impondría el rol de botón a un enlace.

Los iconos proceden de Hugeicons, en el estilo Stroke Rounded, con licencia MIT. Los elegí en lugar de Font Awesome Pro: su licencia es de pago, y la maqueta aprobada está dibujada con Hugeicons.

Dos temas, y el del sistema por defecto

Verre define un tema claro, «alba», y un tema oscuro, «Noche». El oscuro se compone de grises neutros, para las superficies, las líneas y los textos. De Catppuccin, la paleta del antiguo sitio, Verre conserva los acentos: el melocotón de Latte en el tema claro, #FE640B, el de Mocha en el oscuro, #FAB387, y, en la página de inicio, el verde azulado y la lavanda de los títulos de sección y el degradado del título principal. El melocotón se usa en los enlaces, las viñetas y el contorno del foco de teclado.

Los colores ya no forman una paleta completa convertida en clases. Se nombran por su rol, como fondo, cristal, texto o acento, y cada rol recibe su valor en cada uno de los dos temas. Un bloque @theme inline enlaza estos nombres con los de shadcn y con las utilidades de Tailwind: un componente usa bg-background o text-muted-foreground, y el valor sigue el tema mostrado.

Conservé los colores de la maqueta tal cual, incluso donde algunos textos quedan por debajo de los umbrales de contraste WCAG AA, en los dos temas: el design system señala estos contrastes bajos sin corregirlos. Las demás exigencias de accesibilidad se mantienen: navegación con el teclado, foco visible, objetivos táctiles de al menos 44 px en pantallas de menos de 768 px de ancho, enlace para saltar al contenido.

El diseño anterior abría el sitio en el tema oscuro, fuera cual fuera la preferencia del visitante. Elegí el tema del sistema por defecto: usted ya ha indicado su preferencia en su sistema operativo, y la misma elección se aplicará en las tres aplicaciones. El selector sigue ofreciendo «Claro», «Oscuro» y «Sistema».

El tema se conoce antes del primer renderizado

Un script situado en el <head>, antes de las hojas de estilo, lee la elección guardada con la clave aubia-theme y la preferencia del sistema. Establece data-theme para una elección explícita y data-scheme para la preferencia: el CSS aplica el tema correcto sin esperar a React. Sin JavaScript, la media query prefers-color-scheme aplica el tema oscuro cuando el sistema lo solicita. Si el almacenamiento local está bloqueado, su elección solo se mantiene durante la visita actual.

El propio cambio de tema está animado. Tras un clic en el selector, la página espera 200 ms, el tiempo que tarda el indicador del selector en deslizarse hasta la nueva opción, y después el nuevo tema se revela en 650 ms dentro de un círculo que se abre desde el botón, mediante la API View Transition. En modo «Sistema», un cambio de la preferencia produce un fundido de toda la página. Cuando el sistema pide movimiento reducido, el tema cambia de golpe.

El CSS del modo «Sistema» lee data-scheme y no la media query. Chromium ya muestra los colores de la nueva preferencia cuando dispara el evento change: si la página siguiera la media query, habría cambiado antes de que el script capturara el estado inicial de la transición.

Las tarjetas reutilizan el cristal gris de la barra

El efecto de cristal procede de la propiedad backdrop-filter: desenfoca lo que pasa por detrás de un elemento, y un fondo semiopaco tiñe el resultado. En el diseño anterior, este filtro también saturaba los colores, y los de las manchas del fondo atravesaban las tarjetas. El cristal estándar del design system, el de las listas desplegables y las notificaciones, todavía satura los colores, al 160 %, con un desenfoque de 24 px. El de la barra de navegación desenfoca lo mismo, pero elimina todo el color:

css
@utility surface-bar {
  background: var(--bar-glass);
  border: 1px solid var(--glass-line);
  backdrop-filter: var(--bar-blur);
}

--bar-blur vale blur(24px) grayscale(100%), y --bar-glass, un blanco al 80 % en el tema claro y un gris al 70 % en el oscuro: los halos de luz del fondo ya no tiñen este cristal. En el sitio lo reutilizan las tarjetas, las preguntas frecuentes y, en los artículos, la portada y las tablas, sin reflejo al pasar el puntero.

El menú de idiomas se muestra fuera de la barra

Cuando un ancestro ya tiene un backdrop-filter, establece una backdrop root: el filtro de un descendiente ya no ve lo que hay detrás de ese ancestro. El menú de idiomas de la barra, un menú de Base UI, se renderiza en un portal: la barra deja de ser su ancestro, y el menú aplica su propio desenfoque. El cristal de la barra se define en la raíz del documento para que este menú también pueda usarlo.

Este menú solo se monta en el cliente: sus enlaces no figuran en el HTML inicial. Por eso el pie de página repite esos enlaces en un elemento <details> nativo: aparecen en el HTML producido por el renderizado en el servidor, y su apertura funciona sin JavaScript. React añade la navegación con las teclas de flecha y el cierre con Escape o con un clic fuera, y lleva el foco al idioma actual al abrirse.

Halos y una trama de estrellas bajo el contenido

El fondo de las páginas consta de dos capas fijas. La primera dibuja en cada esquina una elipse de luz del color que toma el símbolo del logotipo en esa misma esquina: violeta arriba a la izquierda, rosa arriba a la derecha, coral abajo a la izquierda, melocotón claro abajo a la derecha, más discretas en el tema oscuro que en el claro. La segunda es una trama de formas de 7 px, soles-estrella en el tema claro y estrellas en el oscuro, que la luz de los halos hace visibles. Su espaciado, cercano a 32 px, divide exactamente el ancho del contenido: una columna de formas coincide con cada uno de sus bordes.

En todo el sitio, cada 0,3 a 0,9 segundos, una forma crece y toma un tono del tema, seis como máximo a la vez, y una lupa ilumina las formas cercanas al puntero. El bucle de animación se detiene cuando la pestaña está oculta.

Geist para el texto y para el código

Retiré Chakra Petch. Geist se usa ahora en todo el texto, incluido el título principal de la página de inicio, y Geist Mono en el código, las rutas y los comandos. Las dos fuentes son variables: un solo archivo WOFF2 latino por fuente cubre los pesos de 100 a 900.

Están autoalojadas. El plugin de fuentes de laravel-vite-plugin lee los archivos de los paquetes Fontsource instalados y genera las declaraciones @font-face durante el build; la directiva Blade @fonts las inserta, con su precarga, en todas las páginas. Ninguna solicitud se envía a un proveedor de fuentes.

Con display: 'swap', el texto se muestra primero en una fuente alternativa. Para Geist, el plugin ajusta las métricas de esa alternativa, y el texto apenas se desplaza cuando llega la fuente. Geist Mono conserva una alternativa monoespaciada corriente: el plugin busca en el archivo la categoría de la fuente, monoespaciada o sin serifa, no encuentra ninguna y tomaría Arial como modelo.

El movimiento, y una sola regla para reducirlo

Las duraciones y las curvas de aceleración proceden de variables CSS, --d-* y --ease-*, que leen tanto las hojas de estilo como los scripts. La curva de las microinteracciones, --ease-out, vale cubic-bezier(.2, .8, .2, 1).

Al cargar la página, ningún texto visible parte de opacidad cero, porque Chromium excluye del LCP un elemento invisible. El título principal de la página de inicio entra con un deslizamiento de 14 px. El fundido queda reservado a los textos que se revelan al desplazarse, más abajo en la página, y a los elementos decorativos.

Antes de Verre, las reglas de movimiento reducido afectaban a algunas animaciones y dejaban que se ejecutaran las demás transiciones. Verre las sustituye por una sola, para todo el sitio:

css
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition: none !important;
    animation: none !important;
  }
}

Cada estado se muestra entonces de inmediato. Los colores siguen cambiando, de golpe; los desplazamientos al pasar el puntero y al pulsar pasan por la variante motion-safe: de Tailwind, que deja de aplicarse.

Las animaciones controladas por script escapan a esta regla, como el eslogan de la página de inicio, cuyas frases aparecen carácter a carácter. Leen la preferencia mediante el hook useReducedMotion del registry, construido sobre useSyncExternalStore: se suscribe al evento change de matchMedia, y un cambio de esta configuración durante la visita detiene de inmediato la animación en curso. El servidor no conoce esta preferencia. Por eso el hook devuelve false en el renderizado en el servidor y durante la hidratación, y después lee el valor real, como describe la documentación de React sobre useSyncExternalStore. El HTML del servidor ya contiene la última frase, y el eslogan la conserva con movimiento reducido.

El código de los artículos conserva los colores de Catppuccin

Phiki 2.2 resalta en el servidor los bloques de código de este blog, durante el build, con los temas Catppuccin Latte, en claro, y Mocha, en oscuro. Una clase PHP copia estos dos temas y sustituye sus colores neutros, los del fondo, el texto, los comentarios y la puntuación, por variables CSS de Verre; falla si una actualización de Phiki elimina una de las reglas de color que sustituye, en lugar de servir un tema incompleto. Un bloque de código toma por tanto el fondo del tema mostrado, y solo sus colores de sintaxis siguen procediendo de Catppuccin.

La beta de Aubia es gratuita y sin límite de tiempo. Inscríbase para recibir su invitación.

Únase a la lista de espera

Volver al blog