The web already had a format.
Your site is JSON.
Pages, components, styles, state, and server functions are all JSON and Markdown documents on disk. The compiler prerenders them to HTML, the runtime wakes up the parts that move, and git holds the whole thing.
Property names mirror the DOM. Reactivity is @vue/reactivity. Output is HTML.
The DOM was always the integration layer.
HTML, CSS and JavaScript are three languages describing one tree, and every framework since 1995 has been a strategy for the plumbing between them: JSX, single-file components, a build step that mints an intermediate representation nothing else can read. The DOM already integrates structure, style and behavior, so Jx serializes it and calls that the source. A document names DOM properties, holds its own state, and carries its own styles, so there is no intermediate representation left to learn or to debug.
{
"$id": "Counter",
"state": {
"count": 0,
"increment": { "$prototype": "Function", "body": "state.count++" }
},
"tagName": "my-counter",
"style": { "display": "flex", "gap": "1rem", "alignItems": "center" },
"children": [
{ "tagName": "span", "textContent": { "$ref": "#/state/count" } },
{ "tagName": "button", "textContent": "+", "onclick": { "$ref": "#/state/increment" } }
]
}
That file is the whole component. Studio edits it, the compiler reads it, a model writes it, and git diffs it a line at a time.
The same counter, three ways to look at it.
The first tab is the document you author. The second is the custom element the compiler writes from it,
@vue/reactivity
and all. The third is that element running in this page, and its buttons work. A page with nothing interactive on it emits none of this and ships no JavaScript at all.
{
"tagName": "my-counter",
"state": {
"count": 0,
"increment": {
"$expression": {
"operator": "+=",
"target": { "$ref": "#/state/count" },
"value": 1
}
},
"reset": {
"$expression": {
"operator": "=",
"target": { "$ref": "#/state/count" },
"value": 0
}
}
},
"children": [
{ "tagName": "span",
"textContent": { "$ref": "#/state/count" } },
{ "tagName": "button", "textContent": "+",
"onclick": { "$ref": "#/state/increment" } },
{ "tagName": "button", "textContent": "Reset",
"onclick": { "$ref": "#/state/reset" } }
]
}File-based CMS
Content lives as Markdown and JSON in your repository, with frontmatter schemas saying what a post must have before it counts as one. There is no content database to run and no admin panel to secure, so review, branching and rollback are git's job. The tradeoff is worth saying out loud: an editor needs Studio or a git checkout, because there is no URL and password to hand out.
Reactive framework
Declare state on a document, bind it into the tree with template expressions, and let the compiler decide what has to ship. Interactive components hydrate as islands, one at a time, on real custom elements, and the rest of the page stays HTML and CSS. Reactivity is @vue/reactivity, the published package, unmodified.
Static generator
The build prerenders every page and writes a folder: HTML, CSS, responsive images, sitemap, redirects. A folder goes anywhere, so take your pick of Cloudflare Pages, GitHub Pages, Vercel, or a $5 VPS, an illustrative third-party hosting price and theirs to change. Adapters for cloudflare-workers, cloudflare-pages, node and bun cover the projects that outgrow a static host.
Accounts and data
Some sites need a login. Add the auth and connector extensions, pick a server adapter, and the build emits one small worker beside the pages: sessions at /_jx/auth, your tables at /_jx/data, and per-table permission rules from the roles you declared in project.json. A state entry marked timing server compiles to an endpoint of its own, so keys and queries stay on the server. The pages themselves stay prerendered, so every visitor is served the same HTML and the logged-in view renders in the browser.
A project that needs a login and a table declares both in
project.json
, and the build carries the declarations into the worker it writes.
{
"extensions": ["@jxsuite/connector", "@jxsuite/auth"],
"connections": {
"main": { "provider": "d1", "binding": "DB" }
},
"auth": {
"connection": "main",
"providers": { "github": {} },
"roles": ["admin"]
},
"data": {
"comments": {
"connection": "main",
"ownerField": "author_id",
"permissions": {
"read": "public",
"insert": "authenticated",
"update": "owner"
},
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } }
}
}
},
"build": { "adapter": "cloudflare-workers" }
}Every layer is the same kind of file.
This is the same list the repository README carries, so nothing in it was written for a marketing page. Core packages never depend on an extension and a CI rule enforces it, which is why the first-party extensions in it use the same public hooks yours would.
Routing
File-based.
[param].json
and
[...path].json
catch-alls, enumerated at build time via
$paths
Content
Markdown collections with frontmatter schemas, Jx Markdown directives, relationships
Styling
Design tokens, breakpoints, states and selectors, a forced color-scheme contract
Logic
Reactive state, template expressions, declarative statements, sidecar JS modules
Server
timing: "server"
state entries compile to
POST /_jx/server/<fn>
; secrets never leave the server
Data
D1, Supabase, and SQLite connections; CRUD over
/_jx/data
; additive schema sync via
jx db push
Auth
Better Auth sessions, sign-in flows, and per-table permission rules over
/_jx/auth
Search
A build-time index over your content collections plus a headless browser client
Assets
Responsive image pipeline (WebP/AVIF), sitemap, robots, redirects
Output
Static HTML by default; adapters for
cloudflare-workers
,
cloudflare-pages
,
node
, and
bun
:::::
A model edits the same artifact you do.
The document format is the contract, so there is no second API for machine edits: a generated page is checked and reviewed exactly like a hand-written one.
jx schema
Writes
project.schema.json
and
document.schema.json
into the project root, composed from the core schemas plus a fragment from every enabled extension. Each is one self-contained resource whose every internal reference points into the same file, so a validator resolves it with no
node_modules
, no network and no configuration.
jx validate
One deterministic pass over the whole project:
project.json
, every document under pages, components and layouts, every class file, every extension fragment. It prints one line per violation and exits non-zero, which makes it a CI gate for machine-written documents and a write-check-fix loop for whatever is writing them.
Studio's assistant
Describe a change and the assistant makes it through the same document operations a person uses, so an AI edit lands in the undo stack and in your git diff like any other. Today it is bring your own key: Studio ships no account and no hosted model, and sends nothing anywhere until you connect a provider.
The repository also carries
.claude/commands/jx.md
, a ready-made
/jx
authoring command for coding agents working inside a Jx project.
The primitives already existed.
Five years ago, building this would have meant inventing half a dozen formats and asking everyone to learn them. The browser now ships every primitive it needs, so Jx extends dialects that already have specifications and tooling behind them. Where it departs from one, the spec names the clause and says why, and the alignment tables list every standard the project binds, its conformance class, and the code that answers for it.
JSON Schema 2020-12
A document validates as an instance against a meta-schema generated from W3C webref data, so any 2020-12 validator checks it and your editor autocompletes it. One limit, stated in the spec: Jx declares no
$vocabulary
, so it is not a JSON Schema dialect in the normative sense, and a standards-only processor ignores its reserved keywords.
JSON Pointer, RFC 6901
A
$ref
path is pointer syntax as written,
~0
and
~1
escapes included, with
/
as the sole separator. The four deviations are enumerated rather than glossed: a
$ref
binds a live value off the reactive scope, and a token that matches nothing yields
undefined
instead of failing evaluation the way RFC 6901 section 4 requires.
URLPattern
Dynamic routes and redirects use URLPattern pathname syntax: named parameters, wildcards, catch-alls. The filesystem decides the URL and the pattern decides its shape, and a dynamic route enumerates its concrete paths at build time through
$paths
.
Web Components v1
A component compiles to a custom element, defined and upgraded the way the HTML standard describes. Light DOM is the default, so your page styles reach inside it and browser devtools inspect it with no special panel; slot distribution is emulated there, and a component that wants a real shadow root and real slots asks for one with
$shadow
.
CSS nesting and custom properties
Style keys beginning with a colon, a dot, an ampersand or a bracket are nested selectors, resolved recursively by both the compiler and the runtime. Design tokens are custom properties compiled to
:root
, so any component reads
var(--color-primary)
without importing anything.
Start with one command.
Scaffold a project, then edit the files in whatever you like. From there the
jx
CLI does the rest: dev with live reload, build to
dist/
, preview a build, generate schemas, validate the project. Open the same directory in Studio when you would rather point than type, because the CLI and the desktop app read and write the same files, so moving between them is not a migration.
bun create @jxsuite my-site
cd my-site
bun run dev
One command turns the project into a folder, and what is in that folder decides where it can go.
$ jx build
✓ Compiled pages
✓ Optimized images (webp, avif)
✓ Generated sitemap.xml
✓ Output: ./dist
# Static host? Ship ./dist.
# Accounts or data? Set an adapter, and a worker ships alongside it.