DEV Community

Cover image for I Built a UI Framework That Doesn’t Use a Virtual DOM
Judah Sullivan
Judah Sullivan

Posted on

I Built a UI Framework That Doesn’t Use a Virtual DOM

I Built a UI Framework That Doesn’t Use a Virtual DOM

Introducing Zenith

Over the last few years, I’ve worked extensively with React, Vue, Next, Nuxt, Svelte, and compiler-adjacent tooling. Each of them solves real problems — but they also left me with a recurring question:

What if the compiler already knew everything before runtime ever existed?

That question is what led me to build Zenith.

Zenith is a compiler-first UI framework that intentionally does not use a Virtual DOM, runtime diffing, or reactive boundaries. Not because those tools are bad — but because I wanted to explore what becomes possible when certainty replaces reconciliation.

This post is an announcement, not a sales pitch. Zenith is different by design, and that difference is intentional.


Why I Started Building Zenith

Most modern UI frameworks share a common assumption:

UI updates are a runtime problem.

State changes, components re-run, virtual trees are recreated, and the framework figures out what changed after the fact.

Zenith flips that assumption:

UI updates are a compile-time problem.

Instead of asking “what changed?” at runtime, Zenith asks:

“What could ever change?”

…and answers that before runtime exists.

Once that answer is known, there’s nothing left to diff.


What Zenith Is (and Isn’t)

Zenith is:

  • Compiler-driven
  • Structural by default
  • Predictable and explicit
  • Minimal at runtime

Zenith is not:

  • A Virtual DOM framework
  • A reactive runtime
  • A template engine
  • A drop-in replacement for React or Vue

It’s a different axis entirely.


A Simple Counter Example

Let’s start with something familiar.

React

function Counter() {
  const [count, setCount] = useState(0)

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

This works great — but under the hood:

  • The component function re-executes
  • A new Virtual DOM tree is created
  • React diffs old vs new
  • Then updates the DOM

All decisions happen at runtime.


Zenith

Zenith enforces a strict separation:

  • Behavior lives in <script>
  • Markup only declares bindings
  • No inline expressions
  • No directives
  • No runtime interpretation
<script>
  state count = 0

  function increment() {
    count++
  }
</script>

<button onclick="increment">
  Count: {count}
</button>
Enter fullscreen mode Exit fullscreen mode

That’s it.


What the Compiler Does

At compile time, Zenith:

  • Resolves count as a state cell
  • Resolves increment as a function reference
  • Determines {count} affects a single text node
  • Emits exact instructions:

    • One DOM event listener
    • One direct DOM text update

At runtime:

  • The function runs
  • State mutates
  • The known DOM node updates directly

There is no re-render path.
There is no diffing step.
There is nothing to guess.


Why {} Exists in Zenith

In Zenith, {} does not mean “evaluate an expression.”

It means:

“Bind this node to a compiler-tracked value.”

That’s why these are invalid:

{count + 1}
{someFunction()}
onclick={count++}
Enter fullscreen mode Exit fullscreen mode

Those require runtime evaluation — and Zenith rejects them at compile time.

The compiler must be certain, or it fails.


Components in Zenith Are Structural

One of Zenith’s core constraints is:

Components do not introduce behavior or reactivity.

They are purely structural transforms.

  • No lifecycle hooks
  • No hidden scopes
  • No reactive boundaries

This allows:

  • Slot content to retain its original scope
  • State identity to remain intact
  • The compiler to reason globally

This constraint is not accidental — it’s what makes the model possible.


Why No Virtual DOM?

The Virtual DOM is an elegant solution to a hard problem.

Zenith avoids the problem entirely.

VDOM frameworks:

  • Recompute → then optimize
  • Recover from uncertainty

Zenith:

  • Prevents uncertainty
  • Emits intent, not guesses

That’s not “better” — it’s different by construction.


Current Status

Zenith is:

  • Actively developed
  • Compiler-driven (Rust)
  • Early, opinionated, and evolving

It’s not production-ready yet — and that’s okay.

Right now, Zenith is about exploring:

  • What a compiler can guarantee
  • How much runtime can be eliminated
  • What UI looks like without reactive boundaries

Why I’m Sharing This Now

Zenith started as a question.

It grew into a compiler.

Now it’s a framework.

I’m sharing it early because:

  • I value architectural discussion
  • I want ideas challenged
  • And I believe UI tooling still has unexplored space

If you’re curious, skeptical, or opinionated — that’s exactly the audience I want to hear from.


Final Thought

Zenith isn’t trying to replace React or Vue.

It’s asking a different question:

What if the compiler already knew everything?

Thanks for reading — more to come.

Top comments (0)