Skip to content
K

Stencil

Naos vs. Stencil — the closest compile-to-Web-Components neighbor.

Naos and Stencil

Stencil is the closest established neighbor to Naos: both are compilers that take TypeScript and JSX and emit native Custom Elements. If you are choosing a compile-to-Web-Components toolchain, this is the most direct comparison on this site.

The difference is not the category — it is what happens inside the generated element, and how mature each project is.

Positioning snapshot from July 2026. Naos is a v0.1 prerelease; Stencil is a production compiler with years of ecosystem behind it. This page explains where Naos fits, not a ranking of a mature project against an MVP.

At a glance

NaosStencil
One-line pitchTSX compiler for native interface elementsCompiler that builds reusable, scalable component libraries
Output targetNative Custom Elements, Shadow DOM, slots, part, DSD, form-associatedNative Custom Elements, Shadow DOM, slots
Authoring unitExported PascalCase functionsClasses with @Component, @Prop, @State decorators + render()
ReactivityExplicit signals: state(), computed(), effect()Decorated props/state; render() re-runs on change
Update modelGenerated direct DOM code per elementLightweight virtual DOM (snabbdom-derived) diffing
Runtime modelSmall shared, tree-shakeable execution kernel; no framework runtimePer-component runtime with a lightweight virtual DOM diff step
CompilerRust / OXC core over an N-API boundaryTypeScript compiler
Framework interopNative custom-element interop; consumer uses the tag directlyAuto-generates React / Vue / Angular wrapper packages
SSR / prerenderDeclarative Shadow DOM prerender + hydrationhydrate module, SSR, and static prerendering
BatteriesOptional layer: primitives, router, data, motionFocused on the component compiler; Ionic ecosystem alongside
Maturityv0.1 prereleaseProduction, widely deployed

Common ground

  • A compiler, not a runtime library — JSX/TSX in, custom elements out.
  • Native Custom Elements with Shadow DOM, slots, and CSS-based theming.
  • TypeScript-first authoring with typed props.
  • Framework-neutral output as the whole point: ship one component, consume it anywhere.
  • Server-side output paths (Stencil's hydrate/SSR; Naos's Declarative Shadow DOM prerender).

This shared ground is exactly why Stencil is the fairest yardstick for Naos.

Where they diverge

Update model — VDOM vs. no VDOM

Stencil renders each component through a lightweight virtual DOM (a snabbdom-derived implementation): render() returns a fresh tree, Stencil diffs it, and patches the differences. It is small and fast, but it is a per-component runtime and a diff step.

Naos has no virtual DOM. The compiler generates direct DOM instructions per element, and fine-grained signals update only the exact text nodes and attributes that changed — there is no re-render of a render() method and no tree diff.

Authoring model

  • Stencil is class-and-decorator: @Component({ tag: 'my-el' }) on a class, @Prop() / @State() fields, and a render() method.
  • Naos is function-and-signal: an exported PascalCase function is the component, state is state(), derived values are computed(), and the TypeScript function name becomes the kebab-case tag.

Framework interop strategy

  • Stencil ships an output-target system that auto-generates typed React, Vue, and Angular wrapper packages around the custom elements, so each framework gets an idiomatic component.
  • Naos leans on native custom-element interop directly — the consuming app uses the tag as-is. (React 19's improved custom-element support makes this smoother than it used to be.) There is no wrapper-generation layer today.

Compiler implementation

  • Stencil's compiler is TypeScript.
  • Naos owns compiler semantics in Rust / OXC behind an N-API boundary, keeping the TypeScript packages thin (types, authoring stubs, Vite glue).

Side by side

Naos — a function that compiles to a Custom Element:

import { state } from "@naos-ui/core"

export function Counter() {
  const count = state(0)
  return (
    <button part="button" onClick={() => count.set(count() + 1)}>
      Count: {count()}
    </button>
  )
}

Stencil — a decorated class with a render() method:

import { Component, State, h } from "@stencil/core"

@Component({ tag: "my-counter", shadow: true })
export class MyCounter {
  @State() count = 0

  render() {
    return (
      <button part="button" onClick={() => this.count++}>
        Count: {this.count}
      </button>
    )
  }
}

Which one fits

Reach for Stencil when:

  • you want a battle-tested, production Web Component compiler with a large ecosystem (and the Ionic lineage behind it)
  • you want generated React / Vue / Angular wrapper packages so each host framework gets idiomatic components
  • you are comfortable with class + decorator authoring and a lightweight VDOM under the hood
  • you need mature SSR/hydration and prerendering today

Reach for Naos when:

  • you want no virtual DOM at all — direct DOM updates driven by fine-grained signals
  • you prefer function + signal authoring over classes and decorators
  • you want Declarative Shadow DOM output and form-associated elements as first-class compiler concerns
  • a Rust/OXC compiler core and a deliberately narrow, statically analyzable authoring surface appeal to you
  • you are comfortable adopting a v0.1 prerelease for that trade

Honest caveats

  • Naos is a v0.1 prerelease; Stencil is production software with a long track record. This comparison is about design direction, not readiness parity.
  • Stencil's "lightweight VDOM" is genuinely small; "no VDOM" is an architectural difference, not automatically a performance verdict for every workload.
  • Treat this as a July 2026 snapshot and re-check both projects' current docs.

Sources