Start a new app

The fastest way to a running Zoijs app is one command:

npm create zoijs@latest my-app
cd my-app
npm install
npm run dev

That scaffolds a named app, installs @zoijs/core, and serves it. npm run dev runs a tiny zero-dependency static server (no bundler, no build step):

  Zoijs dev server

  - Local:  http://localhost:7310

It falls back to 7311 / 7312 / 7313 if the port is busy.

The generated files are plain HTML, CSS, and JavaScript you can open and read.

create-zoijs is the official Zoijs starter: a tiny scaffolder, not a toolchain. It writes a few files and gets out of the way.

You don't need the CLI. Zoijs works perfectly without it — it's only a convenience. The no-CLI setup below is just as valid.

Naming your app#

The folder name you pass becomes the project name:

npm create zoijs@latest task-manager

produces a package.json with:

{ "name": "task-manager" }

and an index.html with a readable title:

<title>Task Manager</title>

If you don't pass a name, the CLI asks one simple question — Project name:.

Templates#

npm create zoijs@latest my-app                       # default: the "app" template
npm create zoijs@latest my-app --template app        # a small task dashboard
npm create zoijs@latest my-app --template basic      # a basic counter
npm create zoijs@latest my-app --template typescript # type-checked JS, no build
npm create zoijs@latest my-app --template minimal    # two files, CDN, no install
npm create zoijs@latest my-app --template library    # author a Zoijs package

app (default)#

A small task dashboard that demonstrates the whole core in one readable app: createState for the list, computed for the "tasks left" count, each for keyed rendering, and component communication — the parent passes data down to a Header, and TaskItem reports toggle/delete events back up through callbacks.

basic#

A basic counter — one createState, one html template, one mount. A small, local app with a dev server, ideal as a starting point.

typescript#

The basic counter as type-checked JavaScript: every file starts with // @ts-check, and a strict tsconfig.json plus the types shipped in @zoijs/core give you full TypeScript safety and npm run typecheck — with no build step (the browser runs the .js as-is). TypeScript-grade safety without a compiler.

minimal#

The smallest scaffold — two flat files (index.html + app.js) that load @zoijs/core from a CDN. No package.json, no install, no dev server. Run it with npx serve . -l 7310. A true blank canvas.

library#

A starter for authoring a Zoijs-based packagesrc/index.js plus a hand-written index.d.ts, an exports/types package.json with a peer dependency on @zoijs/core, and a node:test suite. The same shape the official optional packages use.

The app, basic, and typescript templates depend only on @zoijs/core; minimal uses no install at all; library builds on the core as a peer dependency.

Project structure#

The generated app is a handful of plain files — nothing hidden:

my-app/
  index.html            import map + the #app mount point
  package.json          app name + the dev script
  dev-server.mjs        tiny zero-dependency static server (npm run dev)
  src/
    app.js              your app: state, markup, mount
    style.css           plain CSS (light/dark, responsive)
    components/          (app template only) small components
      Header.js          parent to child: hero + summary
      StatCard.js        parent to child: a reusable stat card
      TaskItem.js        child to parent: reports events via callbacks

The basic template is the same without the components/ folder. Open any file and read it — that's the whole project.

What the CLI does#

  • Copies a template and fills in your app's name (in package.json, the <title>, and the README).
  • Validates the name (npm-safe) and refuses to overwrite a non-empty folder.
  • Prints the three commands to get going.

What the CLI does not do#

By design, to keep Zoijs simple, there is no build system, bundler, compiler, JSX transform, code generator, plugin system, template DSL, or complex prompts. The generated app is plain files — nothing is hidden.

Set up by hand (no CLI)#

The CLI just saves typing. Here's the same thing by hand.

With npm#

mkdir my-app && cd my-app
npm init -y
npm install @zoijs/core

Create index.html with an import map pointing at the installed package:

<!DOCTYPE html>
<html>
  <head>
    <title>My App</title>
    <script type=class="tok-string">"importmap">
      { "imports": { "@zoijs/core": "./node_modules/@zoijs/core/src/index.js" } }
    </script>
  </head>
  <body>
    <div id=class="tok-string">"app"></div>
    <script type=class="tok-string">"module" src=class="tok-string">"./app.js"></script>
  </body>
</html>
// app.js
import { html, mount, createState } from "@zoijs/core";

function App() {
  const count = createState(0);
  return html`<button onclick=${() => count.set(count.get() + 1)}>Clicked ${() => count.get()} times</button>`;
}

mount(App, "#app");

Serve it with any static server (npx serve . -l 7310) and open the printed URL.

With a CDN (zero install)#

No package.json at all — point the import map at a CDN and pin the version:

<script type=class="tok-string">"importmap">
  { "imports": { "@zoijs/core": "https://esm.sh/@zoijs/core@1" } }
</script>

Everything else is the same. This is the most minimal possible setup.

Next steps#