Philosophy

HTML is the starting point. It is the most successful document format ever shipped: declarative, resilient, readable by anyone, and rendered by everything.

Almost everything a website needs is already there.

Almost. A handful of things are missing:

  • Layouts - no way to share a shell between pages
  • Components - no HTML-only way to define a tag once and reuse it
  • Simple interactivity - wiring a button to an outcome means leaving HTML for imperative DOM code
  • State - no memory between interactions or pages
  • Dynamic content - no way to render a value into markup, repeat an element for each item in a list, or show something only when a condition holds
  • Scoped styles - every stylesheet is global, so two components using the same class name collide
  • Navigation - every link throws the whole page away and starts again. There is no way to update part of a page in place or move between pages without a full reload, which is why htmx exists

Every framework of the last decade answered by replacing HTML with JavaScript. Components became functions, markup became a return value, and the page became something a runtime computes: HTML as a function of JavaScript.

JAF turns that on its head. JavaScript is a feature of HTML here, not the other way around.

You keep writing .html files, links stay links, and the missing pieces above are added to the language you already know. JAF is additive, never a replacement.

HTML in, HTML out

The symmetry matters: what starts as HTML exports as HTML.

The production build prerenders your pages to real markup, so the browser paints a document first and hydrates behavior after. No blank shell waiting for a bundle, and nothing a crawler cannot read.

The platform comes first

Where the web platform has an answer, JAF uses it and ships sugar, not a reimplementation.

Modals sit on the native dialog top layer, not a z-index stack with a JavaScript focus trap. Popovers use the Popover API and CSS anchor positioning, not a positioning engine. Animation belongs to CSS and the View Transitions API, not a JavaScript timeline.

JavaScript is reserved for coordination the platform cannot express: reactivity, lifecycle, and wiring.

When a new platform capability lands, we treat it as a chance to delete framework code, not to wrap it.

HTML is the component

A JAF component is an .html file: markup, a <script>, and a <style>, side by side. There is no JSX and no render function.

What JAF adds to the markup - JEX - is deliberately small: JavaScript expressions in curly braces, plus a handful of attributes and elements for the things HTML cannot say.

The host language stays HTML: class is spelled class, and onclick="save()" means what it says.

Reactivity follows the language instead of an API: let is reactive, const is static.

Write count++ and the page updates. No hooks, no setters, no subscriptions.

Compiler over runtime

Work that can happen at build time should never happen in the browser. The compiler analyses your components, wires the reactivity, and ships the smallest runtime it can.

JAF has no runtime framework dependencies, and components need no imports: drop a file in components/ and use the tag.

Why React and JetShake

No new framework can replay twenty years of ecosystem.

The largest library of ready-made components, design systems, and battle-tested UI code in existence is written for React, and pretending otherwise is how frameworks stay toys.

JAF's answer is JetShake, an optimizing compiler that takes React-flavoured .tsx components and compiles them to fast native code with no React runtime.

Drop a JSX component next to your .html ones and use the tag: same auto-discovery, same props, no imports, no virtual DOM tax. You get the third-party ecosystem without shipping the framework it was written for.

This is a deliberate division of labour: JAF stays a light wrapper over the platform, and JetShake is the bridge that lets the existing component world in.

Write your app in HTML; borrow from the React ecosystem when it saves you a week.

Speed matters

Performance is a feature, not an afterthought.

The target is simple to state and hard to reach: fine-grained updates fast enough to stand next to the fastest compiled frameworks, measured honestly on public benchmarks, not claimed in marketing copy.

Fail loudly or work completely

A framework that silently does nothing is worse than one that throws.

Every feature should either work as documented or say clearly why it cannot. When we find a silent failure, we treat it as a bug in the framework, and then as a gap in the test suite that let it ship.

Opinionated defaults, escape hatches everywhere

Routing, state, data fetching, overlays, and scoped styles all work out of the box with sensible defaults, because most apps need the same things.

But every default can be opted out of, and nothing stops you reaching for the platform directly. It is your page, not ours.