Durante años, hablar de Swift para la web ha significado casi siempre hablar del lado servidor. Vapor y Hummingbird permiten construir servicios HTTP, APIs y aplicaciones completas sin abandonar el lenguaje, pero la interfaz que recibe el navegador continúa escribiéndose normalmente con HTML, CSS y JavaScript. ElementaryUI propone dar un paso más: compilar Swift a WebAssembly y ejecutar en el navegador una interfaz declarativa y reactiva inspirada en SwiftUI.
El proyecto no ha aparecido de la nada. Su base es Elementary, una librería creada por Simon Leeb para generar HTML desde Swift en el servidor. ElementaryUI reutiliza esa misma sintaxis de elementos HTML, pero sustituye la salida de texto por nodos reales del DOM y añade estado, eventos, ciclo de vida y animaciones. Son dos piezas relacionadas, aunque todavía conviene entenderlas por separado.
Elementary: HTML tipado sin motor de plantillas
Elementary convierte las etiquetas HTML en tipos y funciones de Swift. En lugar de cargar una plantilla, interpolar cadenas y esperar que el resultado sea válido, se compone un árbol con un result builder muy parecido al que utiliza SwiftUI. Las etiquetas conservan sus nombres originales en minúsculas, por lo que el código sigue recordando al HTML que finalmente llegará al navegador.
import Elementary
struct Recipe {
let slug: String
let name: String
let preparationTime: Int
}
struct RecipePage: HTMLDocument {
let recipes: [Recipe]
var title = "Recetas de la semana"
var head: some HTML {
meta(
.name(.description),
.content("Una selección de recetas rápidas")
)
}
var body: some HTML {
main {
h1 { "Recetas de la semana" }
ul {
for recipe in recipes {
li {
a(.href("/recetas/\(recipe.slug)")) {
recipe.name
}
span { " · \(recipe.preparationTime) min" }
}
}
}
}
}
}
La semejanza con SwiftUI está en la composición, no en el resultado. RecipePage no describe una jerarquía de vistas nativas de Apple, sino un documento HTML. El compilador puede comprobar los tipos de la estructura, las condiciones y los componentes reutilizables antes de que el servidor atienda una petición.
También hay un sistema de atributos que conserva información sobre la etiqueta a la que pertenece cada uno. Un href tiene sentido en un enlace y autofocus puede propagarse hasta el elemento compatible que encapsula un componente. Además, class y style se combinan de forma predecible cuando varios niveles de composición aportan valores.
La decisión técnica más interesante está en lo que Elementary consigue evitar. La librería no depende ni siquiera de Foundation, no utiliza reflexión en tiempo de ejecución y prescinde de contenedores existenciales intermedios. Su implementación genérica puede renderizar directamente una cadena o enviar fragmentos de HTML a medida que se producen. Esta segunda opción permite que el navegador comience a recibir la respuesta sin esperar a que la página completa se haya acumulado en memoria.
Elementary permite renderizar contenido de forma asíncrona con Swift Concurrency y ajusta el ritmo de envío a la velocidad con la que el cliente puede recibirlo. Es posible esperar datos dentro del contenido o recorrer un AsyncSequence con AsyncForEach, escribiendo cada elemento cuando esté disponible. Elementary se mantiene deliberadamente independiente del servidor, aunque dispone de integraciones comunitarias para Hummingbird y Vapor.
Esta sencillez marca también sus límites: Elementary no incluye estado reactivo, sistema de diseño ni motor de CSS. Su trabajo consiste en transformar valores Swift en HTML de manera eficiente. Para añadir interactividad desde el servidor se puede combinar con HTMX; para ejecutar Swift dentro del navegador entra en juego ElementaryUI.
ElementaryUI: el mismo lenguaje, otro lugar de ejecución
ElementaryUI toma los tipos HTML de Elementary y los integra en un entorno de interfaces reactivas. El código Swift se compila mediante el SDK de Swift para WebAssembly, se carga como un módulo .wasm y se ejecuta en el entorno aislado del navegador. JavaScriptKit y un pequeño bloque de código de arranque proporcionan el puente hacia las APIs de la plataforma web.
La presentación de Simon Leeb en Swift @ FOSDEM 2026 resume bien las capas: HTML continúa definiendo la estructura, CSS controla la presentación y WebAssembly ejecuta la lógica escrita en Swift. JavaScript no desaparece de la plataforma; ElementaryUI evita que tenga que ser el lenguaje principal de la aplicación y lo utiliza como enlace con el navegador cuando resulta necesario.
Para un desarrollador de SwiftUI, la API resulta deliberadamente familiar. Una estructura marcada con @View declara un body, @State conserva el estado local y los modificadores registran eventos del DOM. Al cambiar una propiedad observada, ElementaryUI vuelve a evaluar las vistas afectadas y sincroniza con el DOM solo los cambios necesarios.
import ElementaryUI
@View
struct HydrationTracker {
@State var glasses = 0
var body: some View {
main {
h1 { "Agua de hoy" }
p { "\(glasses) de 8 vasos" }
if glasses < 8 {
button { "Añadir un vaso" }
.onClick {
glasses += 1
}
} else {
p { "Objetivo completado" }
button { "Empezar de nuevo" }
.onClick {
glasses = 0
}
}
}
}
}
@main
struct HydrationApp {
static func main() {
Application(HydrationTracker())
.mount(in: "#app")
}
}
Las vistas son valores temporales, como en SwiftUI. ElementaryUI crea nuevas descripciones cuando cambia el estado, mientras conserva el almacenamiento de @State durante el ciclo de vida de la vista base. Cuando esa vista sale de la jerarquía, su estado se libera. @Binding permite que un componente hijo lea y modifique un valor cuyo propietario está más arriba de la jerarquía.
Para modelos de estdo existe @Reactive, una alternativa a @Observable diseñada para funcionar también con Embedded Swift. El seguimiento es granular: una vista que lee una propiedad vuelve a evaluarse cuando esa propiedad cambia. @Environment completa el modelo para propagar valores o modelos reactivos por la jerarquía sin pasarlos manualmente a través de cada inicializador.
ElementaryUI incorpora además eventos tipados del DOM, enlaces para campos de entrada y modificadores de ciclo de vida como onAppear, onDisappear, onChange y task. Las tareas asíncronas iniciadas con este último quedan ligadas a la vista y se cancelan cuando desaparece, una semántica que vuelve a resultar muy reconocible para quien ya trabaje con SwiftUI.
No es SwiftUI dibujando una web
La sintaxis compartida puede inducir a pensar que ElementaryUI intenta trasladar SwiftUI al navegador. En realidad, el proyecto insiste en seguir siendo un marco web. Un div es un div, los eventos proceden del DOM y el diseño sigue perteneciendo a CSS. Esto evita ocultar la plataforma bajo una capa que termine siendo difícil de atravesar cuando una aplicación necesite una capacidad específica del navegador.
La consecuencia es que conocer SwiftUI reduce la curva de aprendizaje del modelo de estado y composición, pero no reemplaza los conocimientos de desarrollo web. Continúan importando la semántica del HTML, la accesibilidad, el diseño adaptable, la cascada de CSS, el historial del navegador y el comportamiento de la red. Tampoco se puede copiar una vista de una aplicación iOS y esperar que funcione sin cambios: se comparte una forma de razonar, no un conjunto de controles nativos.
El sistema de animación refleja ese equilibrio. ElementaryUI utiliza transacciones, withAnimation y animation(_:value:) para describir cuándo deben animarse los cambios, pero delega su ejecución en mecanismos del navegador. Incluye curvas temporales y efectos de muelle ya familiares, modificadores como opacity u offset, transiciones… El sistema conserva la velocidad cuando una animación se interrumpe y permite encadenar actualizaciones sin que cada una comience desde cero.
Embedded Swift cambia el tamaño de la ecuación
Compilar una aplicación Swift completa para WebAssembly puede generar módulos demasiado pesados para una página web. Descargar varios megabytes antes de mostrar una interfaz pequeña anularía buena parte del atractivo del enfoque. ElementaryUI afronta este problema con compatibilidad para Embedded Swift, el subconjunto experimental del lenguaje orientado a reducir el tiempo de ejecución y eliminar metadatos que la aplicación no necesita.
La documentación oficial de Swift explica que los SDK para WebAssembly incluyen dos destinos: uno con todas las características del lenguaje y otro terminado en -embedded. Este último puede producir binarios varios órdenes de magnitud menores. ElementaryUI adapta piezas como su sistema @Reactive precisamente porque Observation y otras APIs habituales no están disponibles tal cual en ese modo reducido.
La reducción tiene un precio. Embedded Swift no ofrece aún todas las características de Swift y obliga a diseñar algunas APIs con más cuidado. Por ejemplo, ForEach necesita una key estable para reconocer cada elemento cuando una lista cambia y, en ElementaryUI, esa clave solo puede ser un valor convertible a texto, como String o Int. También siguen evolucionando áreas como la serialización, la interoperabilidad con JavaScript y el acceso a las APIs del navegador. Los módulos pequeños no son magia: son el resultado de trabajar con un entorno de ejecución más limitado.
El entorno de desarrollo sigue teniendo piezas web
La versión recomendada parte de Swift 6.3 o posterior, un SDK de WebAssembly que coincida exactamente con la versión de la herramienta y Node.js 22 o posterior. El proyecto ofrece una plantilla de Vite que reúne un paquete Swift y la configuración web necesaria. Tras la primera compilación, Vite vigila los archivos Swift, recompila el módulo y recarga la aplicación durante el desarrollo.
npx degit elementary-swift/starter-vite my-elementary-project
cd my-elementary-project
npm install
npm run dev
Vite no sustituye a Swift Package Manager. SwiftPM resuelve y compila el código Swift; Vite organiza los recursos web, ejecuta el servidor de desarrollo y genera el paquete que se desplegará. Incluso en una aplicación escrita casi por completo en Swift siguen existiendo un index.html, estilos CSS y una pequeña capa JavaScript que inicializa WebAssembly.
Ese detalle es una virtud más que una contradicción. ElementaryUI no inventa un navegador alternativo: se integra con el ecosistema existente. Puede utilizar Tailwind, CSS tradicional o ElementaryFlow, una librería complementaria que expresa estilos CSS mediante una API Swift tipada.
Del servidor al navegador: la pieza que todavía falta
Elementary y ElementaryUI comparten los tipos HTML, pero todavía no ofrecen un flujo completo para generar una página en el servidor y activar después su parte interactiva en el navegador. Es decir, no se puede renderizar una jerarquía con Vapor o Hummingbird y hacer que ElementaryUI reutilice automáticamente esos mismos nodos del DOM, les asocie su estado y registre sus eventos.
La documentación de renderizado en servidor recomienda mientras tanto una arquitectura de islas. Elementary genera en el servidor la página indexable y de carga rápida; dentro de ella se reservan contenedores concretos para las partes interactivas; después se montan aplicaciones ElementaryUI independientes en esos puntos. También es posible optar por renderizado completamente en el cliente cuando la interacción domina la experiencia.
Esta separación permite construir proyectos útiles, pero limita la promesa de compartir una interfaz completa entre servidor y navegador. La hidratación, el renderizado estático con islas, los componentes web, la navegación y varias APIs del navegador aparecen en la hoja de ruta hacia la versión 1.0 mostrada en diversas presentaciones.
Un proyecto prometedor, todavía en una fase temprana
Elementary ya resuelve un problema concreto y acotado: generar HTML tipado y transmitirlo con eficiencia desde un servidor Swift. Su ausencia de dependencias y su integración opcional con Vapor o Hummingbird hacen que se pueda adoptar sin aceptar una arquitectura completa.
ElementaryUI es la apuesta más ambiciosa. Su estado reactivo, la actualización incremental del DOM, las animaciones y el soporte de Embedded Swift demuestran que ejecutar una interfaz Swift en el navegador ha dejado de ser solo un experimento técnico. La demostración en FOSDEM, con composición de vistas, eventos y recarga en caliente, enseña un flujo de trabajo sorprendentemente cercano al desarrollo cotidiano con SwiftUI.
Sin embargo, el propio repositorio advierte de que sigue siendo un proyecto personal en desarrollo activo: las APIs pueden cambiar, quedan zonas poco documentadas y todavía faltan piezas importantes para aplicaciones grandes. No parece la elección adecuada para reemplazar de inmediato una interfaz crítica ya consolidada, pero sí una base muy interesante para herramientas internas, proyectos personales, componentes aislados y experimentos full stack en Swift.
Lo más valioso de Elementary no es fingir que la web funciona como una plataforma de Apple. Es aprovechar las fortalezas de Swift —tipado, composición, concurrencia y seguridad— sin renunciar a HTML, CSS, el DOM y las APIs que hacen que la web sea la web.