Quick Start
Build your first What app in under 5 minutes. You'll learn the core concepts by building a simple counter.
Try it online
Want to see What Framework in action? Try the live playground to write and run code in your browser, no install required.
Create a New Project
The fastest way to get started is with our CLI:
npm create what@latest my-app
cd my-app
npm install
npm run dev
The CLI asks three questions: which template (SPA or Full-stack), whether to add React library support, and a CSS approach. This page follows the default answers: the SPA template, no React support, vanilla CSS. Open http://localhost:5173 and you'll see your app running.
If you answer differently
The Full-stack template is buildless: it has no Vite config, no index.html and no compiler, its pages are written with h() instead of JSX, and npm run dev serves on http://localhost:3000. Answering yes to React library support replaces the What compiler with the what-react Vite plugin. Everything below assumes the SPA template with the What compiler.
Project Structure
Your new project looks like this:
my-app/
├── src/
│ ├── main.jsx # App entry
│ └── styles.css # App styles
├── public/ # Static assets (favicon.svg)
├── index.html
├── vite.config.js # Vite + what-compiler
├── eslint.config.js # eslint-plugin-what
├── .mcp.json # MCP devtools server
├── CLAUDE.md # Instructions for coding agents
└── package.json
The scaffold also writes tsconfig.json, README.md, .gitignore, AGENTS.md, and editor config in .cursor/ and .vscode/.
Your First Component
Let's build a counter. Open src/main.jsx:
import { mount, signal } from 'what-framework';
function App() {
const count = signal(0);
return (
<div>
<h1>Counter: {count()}</h1>
<button onClick={() => count(c => c + 1)}>
Increment
</button>
</div>
);
}
mount(<App />, '#app');
Save the file. The counter now works. Let's break down what's happening:
signal(0)
Creates a reactive value starting at 0. Unlike React's useState tuple, this returns one function: call it to read, call it with a value to write. signal() works anywhere, module scope, components, or stores.
{count()} in JSX
Reading the signal in JSX subscribes that text node. When the value changes, only that node updates, not the whole component.
count(c => c + 1)
Updates the signal: call it with the new value, or a function that receives the current value and returns the next. count.set(c => c + 1) is an equivalent explicit form. The DOM updates on the next microtask, and only the text node that reads count changes. Call flushSync() when you need the update to have landed on the very next line, which is mostly what tests need.
No Re-renders
Unlike React, What doesn't re-run your component function when state changes. It updates only the specific DOM nodes that depend on changed signals. This is called fine-grained reactivity.
Adding Computed Values
Let's add a derived value that updates automatically:
import { mount, signal, computed } from 'what-framework';
function App() {
const count = signal(0);
const doubled = computed(() => count() * 2);
return (
<div>
<h1>Count: {count()}</h1>
<p>Doubled: {doubled()}</p>
<button onClick={() => count(c => c + 1)}>
Increment
</button>
</div>
);
}
mount(<App />, '#app');
computed() creates a derived signal. It:
- Tracks which signals it reads (
countin this case) - Caches its value until dependencies change
- Updates automatically when
countchanges
Why not just const doubled = count() * 2?
In React, that works because the entire component function re-runs on every state change. In What, the component function runs once: so count() * 2 evaluates to a plain number at creation time and never updates. computed creates a reactive derivation that stays in sync.
For simple one-off expressions in JSX, the compiler handles it: <p>{count() * 2}</p> works directly. Use computed when you need the derived value in multiple places or want to pass it to child components.
Running Side Effects
Need to do something when a signal changes? Use effect():
import { mount, signal, effect } from 'what-framework';
function App() {
const count = signal(0);
// Runs immediately, then re-runs when count changes
effect(() => {
document.title = `Count: ${count()}`;
});
return (
<div>
<p>{count()}</p>
<button onClick={() => count(c => c + 1)}>+1</button>
</div>
);
}
Effects auto-track their dependencies, no dependency array needed. Any signal read inside the effect becomes a dependency automatically.
Next Steps
You've learned the basics. Here's where to go next: