Jorge Acosta logo Jorge Acosta

La odisea de construir Fluent UI para Svelte

La mayoría de las apps de escritorio hechas con tecnologías web parecen páginas disfrazadas: interfaces plásticas, cero accesibilidad y nada nativas. Quería el diseño real de Microsoft Fluent 2 en mis apps con Tauri y Svelte, pero no existía nada a la altura. Así que me metí en la boca del lobo para construirlo yo mismo.

Published: 15 de octubre de 2026 - Updated: 16 de octubre de 2026

Author: Jorge Acosta

Reading Time: 9mins

Todo empezó con una frustración muy específica...

Figma App Dashboard Design

Estaba diseñando/construyendo aplicaciones de escritorio multiplataforma usando Tauri (y evaluando Electron) con Svelte. La promesa de estas tecnologías es increíble, pero había un problema enorme: la interfaz de usuario.

Si miras la mayoría de las aplicaciones hechas con tecnologías web para escritorio ahí afuera, se nota a kilómetros que son páginas web disfrazadas.

Están llenas de interfaces extrañas que no respetan las convenciones del sistema operativo, con cero accesibilidad (intenta usarlas solo con el teclado y sufrirás) y, siendo honestos, muchas veces son visualmente feas o se sienten "plásticas".

Yo no quería eso. Quería que mis aplicaciones en Windows y escritorio lucieran, se sintieran y se comportaran como una auténtica aplicación de Microsoft (Microsoft si lees esto, no estoy intentando... suplantar/robar/quebrantar/hacerme pasar/o cualquier termino fancy/legal que tus abogados inventen... usar tu imagen, es que FluentUI/WinUI es genial!). Quería la estética limpia, las capas, la precisión... basicamente todo lo que ofrece FluentUI 2. (Pero carajos, como es de complejo!)

Hasta hace poco conversaba con personas del equipo de mantenedores de Svelte. Y de verdad... Que chat tan fantastico. Conversaciones sobre A11y, la manera correcta de categorizar componentes sin mezclarlos con la logica del negocio... Un poco de pelea sobre AI... creo que deberia hacer esto mas seguido 😁😁 realmente ayudo a reafirmar mi postura sobre el a11y y fluentui

La búsqueda infructuosa y el laberinto de la documentación

FluentUI Alternatives

Pensé: "Seguro alguien ya resolvió esto para Svelte". Busqué por todo GitHub y npm. Encontré proyectos abandonados hace años en Svelte 3 (que de hecho lo llegue usar en un Hackaton de Midu.dev hace mucho tiempo, pero no gane el premio 🥲), wrappers incompletos de Web Components que pesaban una tonelada, o librerías que imitaban vagamente el estilo antiguo de Windows 10 (Metro/Fluent 1) pero sin alma ni mantenimiento.

Si quería Fluent 2 real en Svelte, tenía que construirlo yo mismo. 😣

Y ahí empezó el verdadero dolor de cabeza: descifrar la documentación oficial de Microsoft.

Entender Fluent UI a nivel de ingeniería es meterse en la madriguera del lobo

Tienes especificaciones de diseño en Figma, documentación repartida entre Fluent 2 Web, WinUI 3 y el repositorio monstruoso de @fluentui/react-components. Y todos mencionan cosas diferentes para el mismo componente! Incluso tienen nombres distintos en cada documentacion.

Pasé semanas haciendo ingeniería inversa: destripando cómo Microsoft calcula cada token de color, las capaz, cómo manejan las curvas de animación, qué tamaños exactos de padding y border-radius hacen que un botón o un input se sienta "nativo" de Windows 11 y no como un clon barato de Bootstrap (Sin ofender jajaja).

Lo que empezó como "solo necesito unos cuantos componentes para mi app de Tauri" se convirtió en meses de sudor, noches en vela y cientos de horas invertidas. Así nació FluentUI Svelte.

Actualmente el proyecto está en su versión v0.6 (Beta). Funciona de maravilla en producción, aunque todavía hay muchas cosas por pulir y agregar. Pero antes de llegar a la v1.0, quiero compartir la complejidad real de lo que hay debajo del capó.


1. Integración real: Pensando en Svelte, no en React

Traducir la arquitectura mental de React (donde vive el Fluent UI oficial de Microsoft) a Svelte fue el primer gran muro. En React todo son hooks, contextos pesados y renders virtuales. Portar eso literalmente a Svelte habría sido un insulto al compilador. Y puedes encontrar intentos de hacer esto ahi afuera.

Reescribí cada patrón desde cero para que se sintiera 100% nativo:

  • Reactividad: Aprovechando el poder de las Runes ($state, $derived, $effect) para que el estado de cada componente sea quirúrgico. En una app de escritorio (Tauri/Electron), la fluidez a 60fps no es negociable.
  • Composición limpia con Snippets y TypeScript: Nada de configuraciones oscuras. Quería que al importar un componente en tu IDE, el tipado estricto y el autocompletado te dijeran exactamente qué hacer sin tener que vivir pegado a la documentación. (Aun necesita retoques 😅)
  • Simplificar el Layering de FluentUI: Si investigas como funciona el UI te daras cuenta que usan una ideologia de apilamiento de capaz (Layering) que la combinacion de cada una resulta en el efecto que todos conocemos. Fue especialmente retador y aqui va un secreto que ya no sera secreto: intente construir FluentUI/WinUI hace mas de 6 años. Pero en ese momento era imposible recrear los efectos con solo CSS sin recurrir a la reciente (2020) implementacion de CSS Houdini asi que lo descarte.
  • Componentes no opinionados: El hoy en dia casi todas las librerias estan pensadas para que sean componentes con soporte a ser opinionados (puedes desentrañar sus tripas a tu gusto desde tu repositorio), pero esa idelogia no va bien cuando lo que quieres es que tu UI sea literalmente FluentUI o Material Design o tal vez LiquidGlass, solo quieres tomar un componente al que le digas que sucede cuando el usuario interacciona con el sin preocuparte de a11y ni de contraste ni de nada que no sea "que hace ese boton cuando le dan click" y tal vez agregarle un icono. (No me mal entiendan, en muchas ocaciones que una libreria sea opinionada es excelente idea). Pero con fluentui-svelte queria absorber esa complejidad... Tu solo te encargas de "que hace" el componente.

2. Accesibilidad (a11y): Lo que separa un juguete de una herramienta profesional

Una de mis mayores quejas con las librerías UI "bonitas" que abundan en la web es que ignoran por completo la accesibilidad. En una aplicación de escritorio real, el usuario experto vive en el teclado: usa Tab, Shift+Tab, Escape, las flechas direccionales y Enter por memoria muscular.

tabs-es

Aquí es donde se fue más del 60% del tiempo de desarrollo:

  • Navegación por teclado real (Roving Tabindex & Focus Trap): Construir un Dialog, un Menu o unos Tabs visualmente toma un par de horas. Hacer que el foco quede atrapado dentro de un modal, que al cerrarse regrese exactamente al botón que lo invocó, o que las flechas recorran los ítems saltando los deshabilitados... eso toma días de pruebas y errores. Microsoft tiene una libreria para esto: tabster... Pero para saber porque esta libreria no fue la que use para construir fluentui-svelte puedes ver: TabSpot de la locura a la salvacion
  • WAI-ARIA sin fricción: Gestión automática de roles, aria-expanded, aria-selected, aria-controls y lectores de pantalla para que la accesibilidad venga de fábrica, no como un "extra" opcional.
  • Focus Visible al estilo Microsoft: Implementación exacta del anillo de foco de alto contraste de Fluent, que solo aparece cuando navegas con teclado (:focus-visible) y no ensucia la interfaz cuando haces clic con el mouse.

3. CSS al límite: Exprimiendo los últimos features de la plataforma

Para lograr que una interfaz se sienta nativa sin llenar el hilo principal de JavaScript con cálculos de estilos, aposté todo al CSS moderno.

image

  • El sistema de Tokens de Fluent 2: Mapeé la arquitectura completa de Design Tokens de Microsoft a Custom Properties de CSS. Superficies, fondos neutros, colores de marca, sombras de elevación y curvas de desaceleración. Cambiar de modo claro a oscuro es instantáneo.
  • Selectores de nueva generación (:has(), :is(), :where()): Gracias a :has(), los contenedores padres pueden reaccionar al estado de sus hijos (por ejemplo, cambiar el borde de un contenedor completo cuando el <input> interno tiene el foco o un estado de error) con cero líneas de JavaScript. Y con :where() mantengo la especificidad baja para que nunca tengas que pelear usando !important. (Aun trabajo en esto :v )
  • CSS moderno y APIs nativas: Uso de CSS Nesting, funciones de color dinámicas y aprovechamiento de las capacidades de los navegadores modernos (como los Webviews actualizados en Tauri/Edge WebView2) para lograr transiciones y posicionamientos ultra ligeros. Tambien color-mix, field-sizing: content, mask, backdrop-filter y varios otros. Bueno... el unico componente en toda la libreria que tiene un Polyfill es el Expander (pronto lo eliminare).

Expander Component


4. Documentación, CI/CD y el nacimiento de Propsmith

Github CICD

Nadie usa una librería que no entiende cómo integrar. Construir fluentui-svelte.pages.dev/docs fue prácticamente desarrollar un segundo proyecto en paralelo.

Y aquí me topé con una pesadilla clásica de mantenimiento: la duplicación de tipos.

Si cambiaba una propiedad en un componente de Svelte o actualizaba una interfaz de TypeScript, tenía que ir manualmente a la documentación a actualizar las tablas de propiedades, los valores por defecto y las descripciones. Hacer esto dos o tres veces por cada uno de los decenas de componentes no solo era agotador, sino una receta segura para tener documentación desactualizada.

Creando mi propia herramienta: Propsmith

Como no quería pasar mi vida reescribiendo lo mismo tres veces, tuve que hacer una pausa e ingeniármelas para crear otra herramienta open source desde cero: Propsmith.

Propsmith es un extractor de tipos diseñado para leer directamente el código fuente y las definiciones de TypeScript, extraer las propiedades, tipos, valores por defecto y comentarios, y transformarlos en datos estructurados listos para consumir en la documentación.

Construir un extractor de tipos tiene su magia oscura (lidiar con ASTs, tipos complejos, uniones e interfaces heredadas es bastante tricky), pero una vez afinado, funciona de maravilla.

Esto:

/** @propsmith Button */
export type ButtonProps = {
  /** The visual size of the button. */
  size?: Sizes;

  /** Whether the button is disabled. @default false */
  disabled?: boolean;

  /**
   * Visual style of the button.
   * @deprecated Use `variant` instead.
   */
  appearance?: string;
};

export type Sizes = "small" | "medium" | "large";

Se convierte en esto:

| Name             | Type                       | Default | Description                                                        |
| ---------------- | -------------------------- | ------- | ------------------------------------------------------------------ |
| `size`           | `"small"` &#124; `"large"` |         | The visual size of the button.                                     |
| `disabled`       | `boolean`                  | `false` | Whether the button is disabled.                                    |
| ~~`appearance`~~ | `string`                   |         | Visual style of the button. **Deprecated:** Use `variant` instead. |

Ahora, el código fuente es la única fuente de la verdad:

  1. Escribo o modifico el componente y sus tipos una sola vez.
  2. En el pipeline de CI/CD, Propsmith entra en acción automáticamente y extrae toda la metadata de las props.
  3. Se ejecutan las validaciones estáticas (svelte-check), el empaquetado para Vite/SvelteKit y todo se despliega en Cloudflare Pages con la documentación siempre 100% sincronizada al último commit.

5. El camino hacia la v1.0 (y cómo puedes sumarte)

Llegar a esta versión v0.6 ha sido una maratón brutal (y los nuevo 32 issues me daran trabajo). Hoy puedo usar FluentUI Svelte en mis propios proyectos y sentir ese orgullo de ver una aplicación en ventana que parece salida directamente de los laboratorios de diseño de Redmond, con el peso pluma y la velocidad de Svelte.

Pero un sistema de diseño nunca está realmente "terminado". Todavía hay bugs que cazar, casos borde de accesibilidad que perfeccionar y nuevos componentes en la lista de espera.

Si alguna vez has sentido esa misma frustración al crear apps en Svelte o Tauri/Electron, te invito a probarlo:

  1. Explora el proyecto: en GitHub
  2. Revisa la documentación: Sitio oficial
  3. Mira el roadmap y los Issues: Milestone v1

Ya sea que lo uses en tu próxima app, abras un issue cuando algo se rompa, o te animes a enviar un PR, bienvenido a bordo. Hagamos que las aplicaciones hechas con Svelte tengan la interfaz de nivel profesional que se merecen.

PD: Abri mi cuenta en GitHub Sponsors, si esta entre tus posibilidades ayudarme con una donacion, te lo agradecere mucho! Mi GitHub Sponsors Aqui

Favicon

I build products for the web, experiment with ideas, and document what I learn along the way. This blog is my personal notebook of projects, tools, interfaces, and everything I discover while trying to create better experiences. Some posts come from real problems I’ve faced while building, others come from simple curiosity and the desire to understand how things work.

Have something you want to build? Tell me about your idea — maybe we can create something interesting together.

enes