</> Ultima

How does Ultima compare?

Ultima shares ideas with other reactive frameworks, but combines native Web Components, external HTML templates, a declarative DSL based on data paths, and execution with no build step or runtime dependencies.

Introduction

Ultima's general proposition isn't unprecedented: other reactive libraries exist, based on Web Components or running directly in the browser with no required compilation step. The difference is in the combination of architectural decisions: components defined as Custom Elements, templates kept in external HTML files, binding via data-* attributes, expressions restricted to data paths, and direct DOM updates.

This isn't a comparison of popularity, feature count, or community size. The focus is on architecture and development experience.

In short. The conceptually closest project to Ultima is Litedom: both offer reactive components based on Web standards, with no Virtual DOM and no required build. Alpine.js is the closest in the look of its declarative DSL. Lit and Minze are the most direct references for building reactive Web Components. VanJS shares the pursuit of a minimal layer over native JavaScript and the DOM.

Ultima's clearest differentiator is using external .html templates, tied to the component's JavaScript class, with a restricted DSL based on data-* attributes and data paths.

Projects compared

This page compares Ultima with five projects:

React, Vue, Angular and Svelte have a broader scope, ecosystem and set of goals. They serve as a market reference for UI frameworks, but they aren't Ultima's closest architectural peers — which is why they don't appear in the table below.

Comparison table

"Yes" shouldn't be read as automatically better than "No" — every row is an architectural decision with trade-offs, not a scoreboard.

Criterion Ultima Litedom Alpine.js Lit Minze VanJS
Main focusReactive Web Components and applications built with native APIsReactive library for Web ComponentsDeclarative reactivity added to existing HTMLBuilding reactive Web ComponentsSimplified Web Component authoringReactive interfaces built directly in JavaScript and the DOM
Native Custom ElementsYesYesNot as the primary unitYesYesNot as the primary unit
Shadow DOMOptionalSupportedNot the default modelBuilt into componentsDefault (used by every component)Not the default model
ReactivityYesYesYesYesYesYes
Template formatExternal .html fileJavaScript template literalThe page's own HTML with x-* directivesTagged template literal in JavaScriptString/template returned from a JavaScript methodJavaScript functions that create DOM nodes
Declarative DSL in HTMLdata-* attributes:for/:if/@bind directives in the templatex-* directivesBindings and directives in html templateson:event attribute and string interpolationNone; composed through JavaScript functions
Template expressionsDeliberately restricted data pathsJavaScript expressions in a template literalJavaScript expressionsJavaScript expressionsJavaScript expressions (interpolated string)Plain JavaScript
Two-way bindingYes, via data-bind and the native change eventYes, via @bindYes, via x-modelNot as a built-in directive — property binding + manual listenerNot documented as a core featureYes, called "State Binding" in its own documentation
Conditionals<template data-if>, data-else-if and data-else:if/:else directivesx-if (on <template>) and x-showTernary expression in render(), plus the cache() directiveTernary expression in the html methodFunction returning a node or null
Lists<template data-for>:for directivex-forrepeat() or map() directive.map().join('') in the templateIncremental manipulation of real nodes
Keyed reconciliationYes, via : field in data-forYes, documented (:key)Yes, via :key in x-forYes, via repeat() when used (not with map())Not documented as a core featureThe concept doesn't exist — the returned DOM nodes are already the real elements
Virtual DOMNoNoNot based on a Virtual DOMNoNoNo
Required build stepNoNoNoNot strictly; tooling is common in the ecosystemNot strictly, though the official workflow often uses Node/ViteNo
Required npm installNoNoNoNot strictly, but it's the most common install pathNot strictly; npm/CDN are both supportedNo
Runtime dependencyNo third-party library in the coreThe Litedom library itselfThe Alpine library itselfThe Lit libraryThe Minze libraryThe VanJS library
Extra runtime sizeProject-local core; not disclosing a number without a reproducible measurementAbout 3–3.5 kB gzipped, per official docsMeasure or omit; not estimated hereAbout 5 kB minified and compressed, per official docsAbout 3 kB minified and compressed, per official docsAbout 1 kB, per official docs
External HTML per componentYes, a core decisionNoThe HTML is already on the pageNot the defaultNot the defaultNo
Template path relative to moduleYes, via import.meta.urlNot applicableNot applicableNot applicableNot applicableNot applicable
Composes with plain HTMLYesYesYesYesYesYes
Latest version researched0.12.1 (Feb 2020)no commits since 20203.15.123.3.31.10.21.6.1
Community/ecosystemPersonal project, no formal communitySmall project, apparently not actively maintainedWell-established communityEstablished Web Components ecosystemSpecialized, active projectSmall, active project
LicenseCC0 1.0MITMITBSD-3-ClauseMITMIT

Versions and dates checked against official documentation/CDNs on 2026-07-24; they may change after this was published. Runtime sizes were not independently verified in this research (except VanJS) — values per each project's official documentation, subject to change.

How it's done in practice

The same five scenarios, implemented in each framework — real code, adapted from the implementations used in this page's benchmark (tested in the browser, not pseudocode). Ultima stays fixed on the left; pick a scenario and a framework on the right to compare.

Ultima
Other framework

Detailed comparisons

Ultima × Litedom

Litedom is probably the conceptually closest project to Ultima. Both use Web Components standards, offer reactivity and data binding, and skip the Virtual DOM, JSX, and a required build step.

The main difference is in how components are organized. Litedom keeps the template in JavaScript, using template literals and language expressions. Ultima splits the template into an external .html file and restricts the DSL mostly to data paths declared in data-* attributes.

That restriction makes Ultima's template less expressive than arbitrary JavaScript, but also more predictable: logic tends to stay in the component class, while the HTML describes bindings, conditionals and lists. It's also worth noting that Litedom's repository hasn't received commits since 2020 — the features described here still work via CDN, but the project doesn't appear to be actively maintained.

Choose Litedom when

  • you want a small, already-structured library for Web Components;
  • you prefer templates written alongside the JavaScript;
  • you need JavaScript expressions directly in the template;
  • you're fine with a project that has no recent commits.

Choose Ultima when

  • you want to keep the template in a separate HTML file;
  • you prefer a deliberately restricted DSL;
  • you want the component's URLs resolved relative to its own module;
  • you're studying or building an architecture centered on native browser APIs.

Ultima × Alpine.js

Alpine.js is the project closest to Ultima in how its HTML looks. Both use declarative attributes to connect data and elements, build conditionals, repeat lists, and handle forms.

The difference is in the architectural unit. Alpine turns pieces of existing HTML into reactive regions via x-data and other directives. Ultima defines each unit as a real Custom Element, optionally encapsulated in Shadow DOM, with its own class and lifecycle.

Alpine allows JavaScript expressions directly in its directives. Ultima mostly uses data paths, reducing the expressive power available in the template and encouraging logic to stay in the component's JavaScript.

Choose Alpine.js when

  • you want to add progressive interactivity to server-rendered pages;
  • you don't need every region to be a Web Component;
  • you want to write JavaScript expressions directly in attributes;
  • you want a more established ecosystem and more community documentation.

Choose Ultima when

  • native component identity matters;
  • you need Shadow DOM per component;
  • you prefer an external HTML template;
  • you want a DSL with less freedom to run arbitrary code inside the HTML.

Ultima × Lit

Lit is the leading contemporary reference for building reactive Web Components. Its components are real Custom Elements, interoperable with HTML and other frameworks. The library provides reactive properties, encapsulated styles, and efficient declarative templates.

Lit writes templates in JavaScript via tagged template literals. Ultima keeps the HTML in a separate file and describes bindings in data-* attributes.

Lit has greater maturity, ecosystem, documentation, TypeScript integration and development tooling. Ultima offers a more experimental, minimalist experience, aimed at those who want to work directly with their own architecture and less external infrastructure.

Choose Lit when

  • you need a mature solution for reusable components;
  • you want solid TypeScript integration;
  • you need a broad ecosystem, tooling and documentation;
  • you're building a design system or a distributed library.

Choose Ultima when

  • external HTML templates are a requirement;
  • you want to study or control the reactive mechanism itself;
  • you want a small core copied directly into the project;
  • you're fine with the project still having experimental parts.

Ultima × Minze

Minze simplifies building Web Components and offers a base class, reactivity, lifecycle, and tooling to produce reusable components. Like Ultima, it tries to reduce the boilerplate of the native APIs.

Minze's workflow is closer to a ready-made library for building and distributing components, including integration with npm, TypeScript, Vite and Storybook. Ultima is more independent of that ecosystem and organizes a component through an external HTML file and a JavaScript class.

Choose Minze when

  • you want to produce publishable component libraries;
  • you need TypeScript and Storybook;
  • you prefer a specialized tool with an established project workflow.

Choose Ultima when

  • you want to skip the usual packaging workflow;
  • you'd rather copy and version the core directly;
  • the goal is your own application or architectural experiment, not a public component library.

Ultima × VanJS

VanJS and Ultima share an interest in a small layer, with no application transpilation and no Virtual DOM. Both proposals try to stay close to the browser.

The difference is the direction taken. VanJS builds the interface through JavaScript functions that create and connect DOM nodes. Ultima keeps HTML as the template's primary language and uses declarative attributes to tie it to state.

Choose VanJS when

  • you prefer building the DOM tree in JavaScript;
  • you want an extremely small library;
  • the unit of composition doesn't need to be a Custom Element.

Choose Ultima when

  • you want the template to stay as HTML;
  • you want Custom Elements as the architectural unit;
  • you prefer declarative binding via attributes and data paths.

What sets Ultima apart

External HTML templates

Every component can keep its structure in an .html file, separate from the JavaScript class. The template's path is resolved relative to the component's own module with import.meta.url, not relative to the page that uses it.

my-counter.js
super(
    { templateUrl: './my-counter.html', shadowDom: true },
    import.meta.url
);

DSL based on data-* attributes

The DSL mostly works with data paths, not with unrestricted JavaScript execution in the template:

Two-way binding between data and the DOM

data-bind updates properties and attributes from state. On editable elements, the native change event writes the value back to the corresponding path.

example.html
<input data-bind='{"value":"name"}'>
<input type="checkbox" data-bind='{"checked":"active"}'>
<span data-bind='{"textContent":"name"}'></span>

Composing components

Passing objects through the data-state attribute involves JSON serialization and has some limitations.

example.html
<child-component
    data-bind='{"data-state":"profile"}'>
</child-component>

Direct DOM reconciliation

In lists with a valid key, DOM nodes matching the same item are reused and repositioned, preserving identity, focus and internal state when possible. There's no parallel Virtual DOM tree kept in sync.

Conditionals that preserve the active branch

When a conditional chain's winning branch stays the same, Ultima doesn't need to destroy and recreate that branch's whole content. This helps preserve the identity of the elements inside it.

Current trade-offs

Ecosystem

Ultima has no formal community, official npm package, plugin collection, broad IDE integration, or an established job market.

Maturity

The project has parts that are well tested and others that are still experimental or incomplete. The source code is still the primary source of truth.

Tooling

There's currently no experience equivalent to what mature TypeScript tooling, specialized linting, devtools, SSR, hydration, or static generation offer.

State serialized into an attribute

The data-state contract makes declarative communication easy, but it uses JSON serialization. That doesn't naturally represent values like functions, circular references, Map, Set, a Date with its semantics preserved, or undefined.

Large updates

Large or high-frequency state updates can make attribute-based serialization and syncing less suitable — see the real numbers in this comparison's benchmark.

DSL expressiveness

Restricted paths keep the template predictable, but require moving transformations and logic into computed properties or JavaScript methods.

Templates loaded at runtime

External HTML files preserve separation of concerns, but introduce runtime resource loading and handling. The project must be served over HTTP; opening it directly via file:// may not work due to browser policies.

Which one should you choose?

Use Ultima when

Custom Elements and native APIs are the direct goal, external HTML per component is desirable, a small and restricted DSL is preferable, and the project can accept a personal, experimental piece of technology.

Use Litedom when

You want a small, conceptually very close alternative, with JavaScript templates and similar reactive features ready to go.

Use Alpine.js when

You need to add progressive interactivity to existing HTML, working with server-rendered pages.

Use Lit when

You need a mature, widely adopted base for Web Components, with TypeScript, tooling and tested documentation.

Use Minze when

You want a simplified experience for developing and distributing Web Components, with npm, Vite and Storybook integration.

Use VanJS when

You prefer building interfaces directly in JavaScript, with a minimal abstraction and no requirement for Custom Elements as the central model.

Is Ultima a new idea?

Not in the sense of starting a new category. Before and after Ultima began, other projects had already combined reactivity, Web Components, the absence of a Virtual DOM, and execution with no required build step.

Ultima's own proposition is in the combination: external HTML templates, real Custom Elements, optional Shadow DOM, a DSL in data-* attributes, expressions based mostly on data paths, composition through the data-state contract, and direct DOM reconciliation.

An independent implementation, with its own architectural combination of existing ideas.

Recommended definition

Ultima is an HTML-first microframework for reactive Web Components, with no required build step and no third-party dependencies in its core, with external HTML templates and a declarative DSL based on data paths.

We avoid "zero runtime" in an ambiguous sense, since Ultima's own core does execute at runtime. The accurate way to put it: no third-party dependencies in the core; no external library loaded at runtime; no required build step.

Sources and methodology

This comparison considers each project's official documentation available as of 2026-07-24. Features, licenses, sizes and installation methods may change. When a feature isn't clearly documented, it's presented as "not documented," not as fact.