[]previous and next page

Quick start

Create an app, run it, and change a component.

For agentsUpdated

You need Node.js 22.12 or newer and npm.

sh
npm create shadow-app@latest my-app
cd my-app
npm install
npm run dev

Vite prints the address. Open src/App.tsx and change something: the page updates without a reload.

The command asks for a few choices (Tailwind, Biome and other add-ons). Flags answer them up front, after --:

sh
npm create shadow-app@latest my-app -- --tailwind --no-biome

--yes accepts the defaults and --help lists every option. The folder must be missing or empty. Pressing Ctrl-C removes whatever the command created.

The starter#

This is src/App.tsx as generated. It runs here:

Show the code
src/App.tsxtsx
import { useMemo, useStore } from "@shadow-js/core";
import { For, Show } from "@shadow-js/web";

export function App() {
  const [count, setCount] = useStore(0);
  const squares = useMemo(() => Array.from({ length: Math.min(count(), 5) }, (_, i) => (i + 1) ** 2));

  return (
    <main class="app">
      <h1 class="title">ShadowJS</h1>
      <p>Count: {count()}</p>
      <button type="button" class="button" onClick={() => setCount((n) => n + 1)}>
        Increment
      </button>
      <Show when={count() > 0} fallback={<p>Press the button.</p>}>
        <ul>
          <For each={squares()}>{(value) => <li>{value}</li>}</For>
        </ul>
      </Show>
    </main>
  );
}

What the command writes#

Path What it holds
package.json @shadow-js/core and @shadow-js/web, and @shadow-js/vite, vite and typescript for development. Scripts dev, build, preview and typecheck.
vite.config.ts defineConfig({ plugins: [shadow()] }).
tsconfig.json Strict TypeScript with "jsx": "preserve" and "jsxImportSource": "@shadow-js/web".
index.html, src/main.tsx A <div id="app"> and the script that calls render(() => <App />, root).
src/App.tsx, src/index.css The starter component and its styles.

Add it to a Vite project#

Install the runtime and the build tools:

sh
npm install @shadow-js/core @shadow-js/web
npm install --save-dev @shadow-js/vite vite typescript

Register the plugin. It compiles .jsx and .tsx files before Vite's own transforms run:

vite.config.tsts
import shadow from "@shadow-js/vite";
import { defineConfig } from "vite";

export default defineConfig({ plugins: [shadow()] });

Tell TypeScript where the JSX types live. The compiler does the transform, so "preserve" leaves the JSX alone:

json
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "@shadow-js/web",
    "strict": true,
    "moduleResolution": "Bundler",
    "noEmit": true,
    "types": ["vite/client"]
  }
}

Give index.html a container and mount the app:

html
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
src/main.tsxtsx
import { render } from "@shadow-js/web";
import { App } from "./App";

const root = document.getElementById("app");
if (root === null) throw new Error("Missing #app element");

render(() => <App />, root);

render returns a function that removes what it inserted.

Next#

The mental model explains what runs once and what runs again. Project structure shows where each package fits, and Configuration lists every option.