[Component]
Components
A component is an .arcm file. It reads its props with ArcMoon.props(), and you use it like any other block. Use it on as many pages as you like.
Components →ArcMoon · 1.0 stable
Everything you need to build a web page, in one simple language. ArcMoon turns it into plain HTML, CSS and JavaScript.
npm install -g arcmoonOr use it in the browser:
import ArcMoon from "https://cdn.jsdelivr.net/npm/arcmoon@1.1.0/dist/browser.js";
const html = await new ArcMoon({ src: "[p]Hello World[end]" }).compile();import ArcMoon from "https://unpkg.com/arcmoon@1.1.0/dist/browser.js";
const html = await new ArcMoon({ src: "[p]Hello World[end]" }).compile();runtime ${
import { signal } from "arcmoon/reactive";
const count = signal(0);
const add = () => count(count() + 1);
}$
[button = onclick: runtime ${ add }$]
Clicked runtime ${ count() }$ times
[end]This button is built by ArcMoon from the code above. The whole site is .arcm files.
Compared
EJS, Pug, Nunjucks, Handlebars and Liquid fill gaps in HTML. ArcMoon builds the whole page: the markup, its components, their CSS and the code that runs in the browser.
| Feature | ArcMoon | EJS | Pug | Nunjucks | Handlebars | Liquid |
|---|---|---|---|---|---|---|
| Plain HTML output | Yes | Yes | Yes | Yes | Yes | Yes |
| Layouts | Yes slots | Partly includes | Yes extends | Yes extends | Partly partials | Yes layout |
| Components with props and content inside | Yes props, slots | Partly include with data | Partly mixins | Partly macros | Partly partial blocks | Partly render with data |
| CSS that belongs to a component | Yes scoped [style] | No | No | No | No | No |
| JavaScript while the page is built | Yes imports, npm, node:, fetch | Partly JS code, no imports | Partly JS code, no imports | No own expressions | No helpers written in JS | No own expressions |
| Reactivity in the browser | Yes signals, live values | No | No | No | No | No |
| Bundles the page's JS and CSS | Yes esbuild | No | No | No | No | No |
| Safe with templates you didn't write | Yes shows what they do, you trust them | No | No | No | No | Yes can't run code |
| Runs in the browser too | Yes | Yes | Yes | Yes | Yes | Yes |
| Renders on each server request | Partly possible; made for built pages | Yes | Yes | Yes | Yes | Yes |
Why ArcMoon
Everything below is a real example. The HTML under each one is what ArcMoon builds from it, made while this page was built.
[Component]
A component is an .arcm file. It reads its props with ArcMoon.props(), and you use it like any other block. Use it on as many pages as you like.
Components →${ const { name = "friend" } = ArcMoon.props(); }$
[p = class: "greeting"]Hello, ${ name }$![end][import = Greeting: "./Greeting.arcm" !]
[Greeting = name: "Adam" !]
[Greeting!]<p class="greeting">Hello, Adam!</p>
<p class="greeting">Hello, friend!</p>
${ }$
Import JSON, read files with node:fs, fetch from an API or use any npm package. It all runs once, while the page is built.
Build-time code →${
import site from "./site.json";
const year = new Date().getFullYear();
}$
[footer]
© ${ year }$ ${ site.name }$ · ${ site.email }$
[end]<footer>© 2026 Moon Café · hello@mooncafe.so</footer>
signal()
A signal holds a value. Write runtime ${ count() }$ in the markup, and that spot updates when the value changes.
Runtime code →runtime ${
import { signal } from "arcmoon/reactive";
const count = signal(0);
const add = () => count(count() + 1);
}$
[button = onclick: runtime ${ add }$]
Clicked runtime ${ count() }$ times
[end][style]
ArcMoon gives the component's elements an attribute and adds it to every selector. Two components can both use .badge.
Styles →${ const { tone = "new" } = ArcMoon.props(); }$
[span = class: "badge"]${ tone }$[end]
[style]
.badge { padding: 2px 8px; border-radius: 99px; }
[end][import = Badge: "./Badge.arcm" !]
[p]ArcMoon [Badge = tone: "1.0" !][end]<style>
.badge[data-a-1gzy4ad] {
padding: 2px 8px;
border-radius: 99px;
}
</style>
<p>ArcMoon <span class="badge" data-a-1gzy4ad>1.0</span></p>
runtime
Mark an element with arcm-ref and get it with ArcMoon.ref(). npm packages listed in bundle are bundled by esbuild.
Refs →runtime ${
import confetti from "canvas-confetti";
const button = ArcMoon.ref(ArcMoon.defineRef("party"));
button.addEventListener("click", () => confetti());
}$
[button = arcm-ref: "party"]Celebrate[end][for-each]
[for-each] repeats its body for each item. The list can come from a file, an API or code in ${ }$.
Syntax →${
const menu = [
{ name: "Espresso", price: 2.5 },
{ name: "Flat white", price: 3.8 }
];
}$
[ul = class: "menu"]
[for-each = ${ menu }$, as: "item"]
[li]${ item.name }$ · $${ item.price.toFixed(2) }$[end]
[end]
[end]<ul class="menu">
<li>Espresso · $2.50</li>
<li>Flat white · $3.80</li>
</ul>
dist/
One command builds every page. No server needed: put dist/ on any static host.
CLI →$ arcm build pages
dist/
index.html the page, with its CSS and JS inside
menu.html a page with no runtime code
assets/ images and fonts your CSS uses