Markdown that grows into a web app — and stops growing when you stop asking.
HyMarkX (HMX, extension .hmx, CLI hmx) is a Markdown-compatible,
progressively enhanced language for documents, websites, interfaces, and interactive
web applications.
npm install -g hymarkx
hmx build page.hmx
⚠️ Status: alpha (0.0.2). Published and installable, but syntax may still change without migration paths, and HyMarkX must not be used to render untrusted content in production — seeSECURITY.mdand the security audit for exactly why.
Every frame above is generated from real compiler output by
scripts/generate-evolution-svg.mjs — the source, the
rendered preview, the colours, and the byte counts. A test asserts the numbers still match
what the compiler emits, so the picture cannot drift into marketing.
Markdown is the best way to write content and the worst way to build an interface. The moment you need layout, reuse, or interactivity, you fall off a cliff into HTML, CSS, JSX, TypeScript, a component library, and a bundler — all at once, before you needed any of it.
HMX tries to remove the cliff rather than narrow it:
Plain text → Markdown → styled document → components → interactivity → application
You pay for a capability when you use it — in syntax, in learning, in runtime bytes.
Ordinary Markdown is already HMX:
# Hello World
This is my website.
## Projects
- AVA
- HyMarkXComponents are directives, not JSX. A container's fence needs more colons than anything it wraps:
::::grid{columns=3 gap=4}
:::card
## Revenue
$42,500
:::
:::card
## Users
14,302
:::
::::Not this:
<div className="grid grid-cols-3 gap-4">
<Card><CardHeader><CardTitle>Revenue</CardTitle></CardHeader>
<CardContent>$42,500</CardContent></Card>
</div>Values come from frontmatter and resolve at compile time — a page using them still ships zero JavaScript:
---
title: Analytics
---
# {{ title }}Reusable components are .hmx files declaring their props:
---
props:
title: { type: string, required: true }
---
:::note
## {{ title }}
::children
:::Interactivity is native, and compiles to a 591-byte gzipped runtime:
::state{count=0}
:::button{on-click="count = count + 1"}
Increment
:::
Count is {{ count }}.| Markdown first | A plain .md file is a valid HMX document. Enforced by CommonMark + GFM conformance suites in CI — 652/652, unchanged across seven syntax additions. |
| Pay for what you use | A static document compiles to HTML + CSS and zero JavaScript. CSS is emitted only for components actually used. Byte budgets are tests. |
| Safe by default | Rendering an untrusted document does not run code — including interactive ones. Expressions are a restricted pure sub-language with no host access, so window, fetch, and document are compile errors, not sandboxed calls. |
| Framework neutral | React may get a good adapter. The language is not defined by it. |
| Not MDX with more syntax | If a feature makes native HMX approach the verbosity of a conventional frontend, the feature is wrong. |
HMX claims no novelty in combining Markdown with components — MDX, Markdoc, Astro,
Quarto and others got there first. See docs/research/prior-art.md
for an honest account of what each does better and what HMX is actually claiming.
npm install -g hymarkx
hmx build page.hmx # a complete HTML document, plus CSS and JS if used
hmx build page.hmx --fragment # just the fragment, to embed in an existing page
hmx build page.hmx --out - # compile to stdout, assets inlined
hmx check page.hmx # diagnostics only
hmx fmt page.hmx --check # formatting, CI mode
hmx dev . # dev server with live reload| Package | Purpose |
|---|---|
hymarkx |
Install this — provides the hmx command |
@hymarkx/compiler |
Documents to HTML, CSS, and an optional runtime |
@hymarkx/parser |
Markdown + HMX to an AST with real source spans |
@hymarkx/ast |
Node types, spans, diagnostics |
@hymarkx/formatter |
hmx fmt |
@hymarkx/language-server |
LSP for editors |
@hymarkx/cli |
CLI implementation |
pnpm install && pnpm build && pnpm checkpackages/ast node types, spans, visitors, diagnostics
packages/parser source → HMX AST (the only package that may touch micromark/mdast)
packages/compiler analysis, components, styles, expressions, HTML backend, runtime
packages/formatter canonical formatting of HMX constructs
packages/language-server LSP: diagnostics, completion, hover, formatting
packages/cli the `hmx` binary
editors/vscode language contribution, TextMate grammar, LSP client
prototypes/ throwaway experiments kept as evidence
Packages are created when a boundary is real, not to match a diagram.
| Document | What it is |
|---|---|
VISION.md |
Why the project exists and what would make it fail |
SPEC.md |
Normative language specification (v0.0 draft) |
ARCHITECTURE.md |
Pipeline, packages, diagnostics, testing |
SECURITY.md |
Trust modes, threat model, and vulnerabilities found |
docs/security-audit.md |
Every threat walked, with the test behind each control — and the two with no test |
docs/research/performance.md |
Measured baseline: plain CommonMark costs what a bare CommonMark parse costs |
ROADMAP.md |
Phases, exit criteria, and the Markdoc gate |
BACKLOG.md |
Prioritised work, and rejected ideas with reasons |
docs/adr/ |
16 Architecture Decision Records |
docs/guides/ |
Data and expressions, styling, components, interactivity, formatting, dev server, editors |
CONTRIBUTING.md |
Workflow, change control, definition of done |
llms.txt |
Canonical summary for language models, including the mistakes they actually make |
AGENTS.md |
Instructions for coding agents working on this repository |
Point it at llms.txt. It documents the syntax, the built-in components, the
trust model — and, most usefully, what does not exist, so a model does not invent
@state or {% if %} from other languages it has seen.
Editor-specific entry points are included: a Claude Code skill at
.claude/skills/hymarkx/, Cursor rules at .cursor/rules/, and AGENTS.md for Codex.
All three defer to llms.txt rather than restating it, and every example in them is
compiled by the test suite.
Phases 0–9 complete; published to npm as 0.0.2. 1,383 tests. CommonMark 652/652 and GFM 40/40, never regressed across ten phases and eight syntax additions.
| Phase | |
|---|---|
| 0 Research and charter | ✅ |
| 1 Markdown foundation | ✅ |
| 2 Directives, schemas, frontmatter | ✅ |
| 3 Styling | ✅ |
| — "is this just Markdoc?" gate | ✅ passed on measured evidence |
| 4 Expressions | ✅ |
| 5 Authored components | ✅ |
| 6 State, events, runtime | ✅ |
| 7 Developer experience | ✅ |
| 8 TS/TSX interoperability | ✅ |
| 9 Hardening, fuzzing, audit, publish | ✅ |
| 10 Ecosystem | not started |
Phase 9 found two real defects that no existing suite would have caught, both now fixed and regression-tested: a parser hang reachable from untrusted input, found by fuzzing on its first run, and a silent code-span corruption in ordinary prose, found by compiling every Markdown file in this repository against a reference renderer. Both are written up rather than quietly patched, because the pattern is more useful than the individual bug.
The gate at the end of Phase 3 was a genuine stop condition: without a demonstrated path to
native interactivity, HMX would have been Markdoc with different punctuation and should not
have continued. It passed on a working prototype — 492 bytes gzipped against React's 47,750
for the same counter — not on an argument. See ROADMAP.md.
What is deliberately not built yet: named derived state, shared state between siblings,
async or data loading, named slots, TSX interop, and author CSS in document mode. Each is
recorded with its reasoning rather than left as an implied promise.
Generated from the package manifests by
scripts/generate-dependency-graph.mjs, with a test
that regenerates it and compares bytes — adding a dependency without redrawing fails the build.
Edges another path already implies are left out, so every line drawn carries information.
The purple box is the point: only @hymarkx/parser may import micromark, mdast, or any other
Markdown engine. That is ADR-0005, and scripts/check-boundaries.mjs enforces it on every
run, including for editor integrations and benchmarks. Everything downstream speaks the HMX AST
and nothing else, which is what keeps the compiler swappable and the browser-facing packages
free of Node.
Licensed under either of MIT or Apache License 2.0, at your option. Rationale in ADR-0010.
Your content stays yours. Compiler output — and the HMX runtime embedded in that output — imposes no licensing obligation on the documents, sites, or applications you build with HyMarkX.
The name is handled separately: HyMarkX, HMX, .hmx, hmx, and @hymarkx are
project marks, so that .hmx keeps meaning one thing. Forks are welcome under the code
license; see TRADEMARK.md for naming them.
Unless you state otherwise, any contribution you intentionally submit for inclusion is dual licensed as above, with no additional terms.