Gea
Naos vs. Gea — near-identical vocabulary, opposite core bet.
Naos and Gea
Naos and Gea are both compiler-first UI toolchains that describe themselves in similar language: compile-time JSX, no virtual DOM, a small runtime, a Vite plugin, and a headless UI package plus a router alongside the core. On a landing page they can read almost the same.
Underneath the shared vocabulary they make a different core bet. This page explains where the two overlap, where they genuinely diverge, and which one fits which job.
Positioning snapshot from July 2026. Naos is a v0.1 prerelease compiler toolchain; Gea is a further-developed application framework. This page is meant to explain where Naos fits, not to rank a broader framework against an MVP. Gea figures are that project's own published claims.
At a glance
| Naos | Gea | |
|---|---|---|
| One-line pitch | TSX compiler for native interface elements | Batteries-included reactive UI framework |
| Output target | Native Custom Elements — Shadow DOM, slots, part, Declarative Shadow DOM, Form-Associated | Standard DOM — components mount into a container and patch plain elements |
| Reactivity | Explicit signals: state(), computed(), effect() (read by calling) | Proxy-based deep reactivity: mutate directly (this.count++), getters for computed |
| Authoring unit | Exported PascalCase functions | Classes first (Store / Component with template()); functions compile to classes |
| Virtual DOM | None | None |
| Runtime model | Small shared, tree-shakeable execution kernel; no framework runtime | Application framework runtime with proxy reactivity and direct DOM patching |
| JSX | Compile-time, narrow analyzable subset | Compile-time transform |
| Compiler | Rust / OXC core over an N-API boundary | JavaScript compiler in the Vite plugin |
| Update model | Generated direct DOM code per element | HTML string templates + deep proxy + surgical patching |
| Scope | Narrow, analyzable core plus an optional batteries layer (primitives, router, data, motion) | Batteries-included framework (UI, router, mobile, SSR) |
| Distribution contract | The native Custom Element itself, framework-neutral | The Gea app / component model |
| Maturity | v0.1 prerelease | More developed, benchmark-tuned |
Where they read alike
Most of the surface overlap is the shared zeitgeist of the post–virtual-DOM compiler wave (Solid signals, the Svelte compile-time ethos, the Web Components and vanilla revival, the "just JavaScript" reaction to heavy runtimes):
- "Compiler-first" and "no virtual DOM" as the headline promise
- Compile-time JSX with no runtime reconciler
- PascalCase authoring, kebab-case naming downstream
- A small runtime and small bundles as a selling point
- A Vite plugin as the integration path
- A headless / accessible UI package plus a router beside the core
- AI agent skills shipped with the toolchain
- A "write ordinary typed code and it just works" framing
This combination has become the default template for a modern minimal framework, which is why two independent projects can describe themselves in nearly the same words.
Where they diverge
The similarity is mostly on the tagline layer. The core decisions are different.
Output target — the sharpest line
Naos compiles each component to a native Custom Element. The element is the
product: it carries Shadow DOM, slots, part-based styling hooks, Declarative
Shadow DOM prerender output, and an experimental Form-Associated path. You ship
a custom tag that any page — React, Vue, Angular, plain HTML, a CMS — can
consume without knowing Naos exists.
Gea, per its own documentation, renders into standard DOM: a root component
mounts into a container and its template produces plain elements such as div
and button, which Gea patches surgically as proxy state changes. There is no
custom-element registration, Shadow DOM, or slot/part contract in that model.
(Some third-party write-ups describe Gea as emitting "native web components";
Gea's own docs show a plain-DOM mount-and-patch model, which is what this page
relies on.)
That single difference drives most of the rest: Naos is a Web Components distribution tool, Gea is an application framework.
Scope — both ship batteries, with different emphasis
It would be wrong to read Naos as "just a compiler." Alongside the narrow core it ships a real optional layer:
@naos-ui/primitives— around three dozen accessible Custom Elements (dialog, combobox, select, slider, menu, tabs, toast, tooltip, and more), many backed by Zag.js, with a form-associated MVP — the same headless engine family Gea's@geajs/uibuilds on.@naos-ui/router— a real client router: URL-to-element mapping, lazy-loaded route modules, abortable loaders andFormDataactions, params/search, scroll and focus restoration, and route events.@naos-ui/data(plus@naos-ui/data-convex) — SWR-inspired fetch and subscription resources with cache, revalidation,mutate, and ref-counted eviction.@naos-ui/motion— a framework-free motion kernel: spring physics, spring-to-CSS tokens, FLIP, reduced-motion handling, and animation-aware presence timing.
So the scope gap is narrower than the "narrow compiler vs. full framework" caricature. The honest split is one of emphasis and boundary:
- Gea-only today: a first-class mobile package (views, navigation, gestures) and a dedicated SSR package.
- Naos-only today: native Custom Element output with Declarative Shadow DOM and form-associated elements, a dedicated data-resource layer with a Convex adapter, and a standalone motion package.
- The boundary difference: Naos keeps these as optional layers that never fold back into the core — foundations never import product layers, and CI enforces it — whereas Gea presents them as one batteries-included whole.
Naos's README still says it is "not trying to be a general application framework," and that remains true of the core; the batteries live one layer out, on purpose.
Reactivity
- Naos uses explicit signal functions. You read a value by calling it —
count()— and write withcount.set(...). Derived values arecomputed(), side effects areeffect(). The graph is explicit and statically analyzable. - Gea uses proxy-based deep reactivity. You mutate state directly
(
this.count++), and computed values are ordinary getters. The tracking is implicit and ergonomic; the mental model is "just mutate the object."
Authoring unit
- Naos is function-first: an exported PascalCase function is the component, and the TypeScript name is the authoring contract that becomes the tag name.
- Gea is class-first:
StoreandComponentsubclasses, with atemplate()method. Function components are supported and compiled down to classes.
Implementation
- Naos owns compiler semantics in Rust / OXC, exposed to Node through an N-API boundary. The TypeScript packages stay thin (types, authoring stubs, Vite glue).
- Gea implements its compile-time transform in the JavaScript Vite plugin.
Side by side
A counter in each toolchain.
Naos — an exported function that becomes a Custom Element:
import { computed, event, state } from "@naos-ui/core"
export function Counter({ label = "Count" }: { label?: string } = {}) {
const count = state(0)
const text = computed(() => `${label}: ${count()}`)
const change = event<number>("change")
return (
<button
part="button"
onClick={() => {
count.set(count() + 1)
change.emit(count())
}}
>
{text()}
</button>
)
}The generated module registers a Custom Element, synchronizes props and
attributes, dispatches native CustomEvents, and can render into Shadow DOM
with styles and slots.
Gea — a class store plus a class component that mounts into the DOM:
class CounterStore extends Store {
count = 0
increment() { this.count++ }
}
export default class App extends Component {
template() {
return (
<div>
<h1>{counterStore.count}</h1>
<button click={counterStore.increment}>+</button>
</div>
)
}
}Which one fits
Reach for Naos when:
- you are building a design system whose components must survive across frameworks, and the native Custom Element is the stable integration contract
- you ship embedded widgets that must not drag an app framework into the host page
- you want Declarative Shadow DOM prerender output,
part-based theming, or a form-associated element - you still want batteries — router, data resources, motion, accessible primitives — but as optional layers around a small, analyzable core
- you value a Rust/OXC compiler core with a deliberately narrow, statically analyzable authoring surface
Reach for Gea when:
- you are building a full application and want a single batteries-included framework, including mobile (views, navigation, gestures) and a dedicated SSR package
- you prefer direct-mutation, proxy-based reactivity over explicit signals
- you want class-based stores and components
- raw js-framework-benchmark performance for an app rendered into the page is a primary axis
The two are adjacent neighbors, not competitors for the same slot: one distributes framework-neutral custom elements, the other builds applications that patch the page directly. Both ship a comparable batteries layer around that core choice.
Honest caveats
- Naos is a v0.1 prerelease. Several primitives (notably form-associated ones) are explicitly unstable. Gea is further along.
- Bundle-size and benchmark figures cited for Gea are that project's own published numbers and were not independently reproduced here.
- Framework landscapes move quickly; treat this as a July 2026 snapshot and re-check both projects' current docs before relying on any single row.
Sources
- Naos: the project README
- Gea repository: github.com/dashersw/gea
- Gea docs — components: components.md
- Gea docs — getting started: getting-started.md
- Gea site: geajs.com
- "Introducing Gea", Coyotiv: coyotiv.com blog