ArcMoon 1.1.0

Start

Language

Reference

Nothing matches “”.

↑↓ move Enter open Esc close

ArcMoon · 1.0 stable

A template language for web pages.

Everything you need to build a web page, in one simple language. ArcMoon turns it into plain HTML, CSS and JavaScript.

install
npm install -g arcmoon

Or 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();
counter.arcm
counter.arcm
runtime ${
  import { signal } from "arcmoon/reactive";
  const count = signal(0);
  const add = () => count(count() + 1);
}$
[button = onclick: runtime ${ add }$]
  Clicked runtime ${ count() }$ times
[end]
Result · try it

This button is built by ArcMoon from the code above. The whole site is .arcm files.

Compared

More than a template engine

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 ArcMoonEJSPugNunjucksHandlebarsLiquid
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

Seven reasons to write .arcm

Everything below is a real example. The HTML under each one is what ArcMoon builds from it, made while this page was built.

  1. 01
    [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 →
    Greeting.arcm
    ${ const { name = "friend" } = ArcMoon.props(); }$
    [p = class: "greeting"]Hello, ${ name }$![end]
    use-greeting.arcm
    [import = Greeting: "./Greeting.arcm" !]
    [Greeting = name: "Adam" !]
    [Greeting!]
    HTML ArcMoon builds
    <p class="greeting">Hello, Adam!</p>
    <p class="greeting">Hello, friend!</p>
  2. 02
    ${ }$

    Data at build time

    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 →
    data.arcm
    ${
      import site from "./site.json";
      const year = new Date().getFullYear();
    }$
    [footer]
      © ${ year }$ ${ site.name }$ · ${ site.email }$
    [end]
    HTML ArcMoon builds
    <footer>© 2026 Moon Café · hello@mooncafe.so</footer>
  3. 03
    signal()

    Interactive without a framework

    A signal holds a value. Write runtime ${ count() }$ in the markup, and that spot updates when the value changes.

    Runtime code →
    counter.arcm
    runtime ${
      import { signal } from "arcmoon/reactive";
      const count = signal(0);
      const add = () => count(count() + 1);
    }$
    [button = onclick: runtime ${ add }$]
      Clicked runtime ${ count() }$ times
    [end]
  4. 04
    [style]

    Styles that stay put

    ArcMoon gives the component's elements an attribute and adds it to every selector. Two components can both use .badge.

    Styles →
    Badge.arcm
    ${ const { tone = "new" } = ArcMoon.props(); }$
    [span = class: "badge"]${ tone }$[end]
    
    [style]
      .badge { padding: 2px 8px; border-radius: 99px; }
    [end]
    use-badge.arcm
    [import = Badge: "./Badge.arcm" !]
    [p]ArcMoon [Badge = tone: "1.0" !][end]
    HTML ArcMoon builds
    <style>
      .badge[data-a-1gzy4ad] {
        padding: 2px 8px;
        border-radius: 99px;
      }
    </style>
    <p>ArcMoon <span class="badge" data-a-1gzy4ad>1.0</span></p>
  5. 05
    runtime

    Browser code with runtime ${ }$

    Mark an element with arcm-ref and get it with ArcMoon.ref(). npm packages listed in bundle are bundled by esbuild.

    Refs →
    refs.arcm
    runtime ${
      import confetti from "canvas-confetti";
      const button = ArcMoon.ref(ArcMoon.defineRef("party"));
      button.addEventListener("click", () => confetti());
    }$
    [button = arcm-ref: "party"]Celebrate[end]
  6. 06
    [for-each]

    Loops with [for-each]

    [for-each] repeats its body for each item. The list can come from a file, an API or code in ${ }$.

    Syntax →
    loop.arcm
    ${
      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]
    HTML ArcMoon builds
    <ul class="menu">
      <li>Espresso · $2.50</li>
      <li>Flat white · $3.80</li>
    </ul>
  7. 07
    dist/

    Static files, ready to ship

    One command builds every page. No server needed: put dist/ on any static host.

    CLI →
    terminal
    $ 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