Morphos & Kosmesis are here.Explore
PraxisJS

Quick Start

Create a new PraxisJS project and build your first component in minutes.

Quick Start

create-praxisjs scaffolds a project with TypeScript, Vite, JSX, and HMR already configured. It's the fastest way to get started.

npm create praxisjs@latest

The CLI asks, in order:

  1. Project name
  2. Template
  3. Desktop app with Tauri v2? (see below)
  4. Add Kosmesis UI components? (see below)
  5. Add an AI integration? (see AI & MCP)
TemplateWhat's included
Minimalcore, decorators, jsx, runtime
With RouterMinimal + router
FullRouter + store, di, composables, concurrent, devtools
BlogRouter + content: a markdown blog with frontmatter and reactive collections

Start the dev server:

cd my-app
npm install
npm run dev

Flags answer a question ahead of time, in any position after the project name. With npm, put them after -- (npm create praxisjs@latest my-app -- --tauri --kosmesis) so npm passes them to the CLI instead of reading them as its own:

FlagAnswers
--tauri / --no-tauriDesktop app with Tauri v2
--kosmesisKosmesis with Tailwind CSS
--kosmesis=praxisjs-css (or --kosmesis praxisjs-css)Kosmesis with @praxisjs/css (--kosmesis=tailwind works too)
--no-kosmesisNo Kosmesis

Desktop app with Tauri v2

After the template, the CLI asks Desktop app with Tauri v2? Answer yes, or pass --tauri to skip the question (--no-tauri skips it too, with no desktop app). The option works with every template:

npm create praxisjs@latest my-app -- --tauri

npm needs the -- so that it passes --tauri to the CLI instead of reading it as one of its own options.

The CLI runs the official Tauri CLI (tauri init, through npx, pnpm dlx, yarn dlx or bunx) to generate src-tauri/, then adapts the project to it:

FileChange
src-tauri/tauri.conf.jsondevUrl http://localhost:5173, frontendDist ../dist, beforeDevCommand / beforeBuildCommand with your package manager, product name and window title from the project name, version read from package.json, identifier com.example.<name>
src-tauri/src/lib.rsA greet command, registered with invoke_handler
package.jsontauri, tauri:dev and tauri:build scripts, @tauri-apps/api and @tauri-apps/cli (latest 2.x)
vite.config.tsFixed port 5173 with strictPort, clearScreen: false, TAURI_ENV_* env prefix, HMR host for physical iOS/Android devices (TAURI_DEV_HOST), src-tauri left out of the watcher, webview build targets for tauri build
src/greet.tsx (minimal) or src/components/greet.tsxA component that calls the Rust command with invoke("greet", { name }), rendered by src/app.tsx
.gitignore, README.mdTauri build output ignored, a short "Desktop app" section
cd my-app
npm install
npm run tauri:dev    # Vite + native window, with hot reload
npm run tauri:build  # installers in src-tauri/target/release/bundle/

npm run dev still serves the plain web app. In a browser the greet component explains that it needs the desktop window, because isTauri() is false there.

Building the desktop app needs Rust and the system dependencies of your platform. The CLI checks for cargo and, when it is missing, prints what to install (or how to load it into PATH when rustup is installed but the terminal predates it). Generating the project does not need Rust.

Replace identifier in src-tauri/tauri.conf.json with your own reverse domain before you ship.

If the Tauri CLI cannot be downloaded or fails, the CLI says why, removes the partial src-tauri/ and leaves the web project as it would be without --tauri. Run npx @tauri-apps/cli@2 init in the project later and follow the Tauri Vite guide.

UI components with Kosmesis

Kosmesis is the copy-paste component collection for PraxisJS, built on the Morphos primitives. At Add Kosmesis UI components?, pick a styling system, or pass --kosmesis (Tailwind CSS) or --kosmesis=praxisjs-css:

ChoiceComponents are styled with
Yes, with Tailwind CSSTailwind CSS v4 utility classes and class-variance-authority
Yes, with @praxisjs/cssTyped CSS-in-TS with @praxisjs/css, no Tailwind
npm create praxisjs@latest my-app -- --kosmesis

The CLI runs kosmesis init without prompts through your package manager (npx, pnpm dlx, yarn dlx or bunx, with kosmesis@^0.6.0), then kosmesis add button card. That installs the project's dependencies, so the project is ready to run when the CLI ends. Kosmesis:

  • writes components.json and the theme tokens: src/style.css with Tailwind (the template's own styles stay below them), src/lib/kosmesis-theme.ts with @praxisjs/css;
  • adds the @tailwindcss/vite or praxisjsCSS() plugin and the @/* → src/* alias to vite.config.ts and tsconfig.json;
  • wires @IconProvider(LucideSource) (and @Themed(...) with @praxisjs/css) on the App component in src/app.tsx;
  • copies the starter components into src/components/ui/.
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";

Add more components with npx kosmesis add <component>: see the Kosmesis components.

Kosmesis works with every template and with --tauri: the components render in the desktop window as in the browser, and the Tauri vite.config.ts gets the same plugin and alias. If Kosmesis cannot be set up (no network, or the kosmesis release is not reachable), the CLI says why, leaves the project as it would be without --kosmesis and prints the commands to run in the project later.

Existing project

praxisjs add kosmesis sets Kosmesis up in a project you already have, Tauri apps included. It checks that the project depends on @praxisjs/core, installs its dependencies when node_modules is missing, runs kosmesis init and says what to do next:

npx praxisjs add kosmesis

In a terminal, without flags, kosmesis init asks its own questions. With flags it runs without prompts: npx praxisjs add kosmesis --style-system praxisjs-css --components button,card. See the CLI reference for every flag.


Manual setup

Prefer to wire things up yourself? Here's what you need.

1. Install packages

npm install @praxisjs/core @praxisjs/jsx @praxisjs/runtime @praxisjs/decorators
npm install -D @praxisjs/vite-plugin

2. Configure Vite

// vite.config.ts
import { defineConfig } from 'vite'
import { praxisjs } from '@praxisjs/vite-plugin'

export default defineConfig({
  plugins: [praxisjs({ hmr: true })],
})

3. Configure TypeScript

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "jsxImportSource": "@praxisjs/jsx",
    "useDefineForClassFields": true,
    "strict": true,
    "noEmit": true
  }
}

Your first component

// src/Counter.tsx
import { StatefulComponent } from '@praxisjs/core'
import { Component, State, Prop } from '@praxisjs/decorators'

@Component()
class Counter extends StatefulComponent {
  @Prop() initialCount = 0
  @State() count = 0

  onMount() {
    this.count = this.initialCount
  }

  render() {
    return (
      <div>
        <p>Count: {() => this.count}</p>
        <button onClick={() => this.count++}>+</button>
        <button onClick={() => this.count--}>−</button>
      </div>
    )
  }
}

Why {() => this.count} instead of {this.count}?

render() runs once on mount. Without the arrow function, this.count is read once at that moment and never updates. Wrapping it in {() => this.count} creates a live subscription: when count changes, only that specific <p> node updates.

Storybook
Counter — live demo

Mount the app

// src/main.ts
import { render } from '@praxisjs/runtime'
import { Counter } from './Counter'

render(() => <Counter initialCount={5} />, document.getElementById('app')!)

AI & MCP

@praxisjs/mcp gives any MCP-compatible AI assistant live access to praxisjs.org documentation, your project's structure and its project memory. Two AI integrations install the PraxisJS skills and configure the server:

IntegrationAI assistantProject memory fileSkillsMCP server config
Claude CodeClaude CodeCLAUDE.md.claude/skills/praxisjs*/.mcp.json, approved in .claude/settings.json
CodexOpenAI CodexAGENTS.md.agents/skills/praxisjs*/.codex/config.toml

New project

At the "Add an AI integration?" step of create-praxisjs, pick Claude Code or Codex. The CLI ends with a setup prompt to paste into the agent.

Existing project

Run the ai add command in the project root:

npx praxisjs ai add

It ends with a prompt to map the project into the memory. To uninstall an integration, run praxisjs ai remove: see the AI skills guide for what it does and doesn't delete.

MCP server only

To add only the MCP server (without the skills):

npm install -D @praxisjs/mcp
pnpm add -D @praxisjs/mcp
yarn add -D @praxisjs/mcp
bun add -d @praxisjs/mcp

See the MCP Server page for client-specific setup (Cursor, VS Code/Copilot, Windsurf, Codex).

Set up with your agent

The CLIs print these prompts when they install the AI integration; copy them from here too. Restart the agent first (Claude Code: approve the praxisjs server when it asks, check with /mcp).

New project. create-praxisjs ends with it when you picked an integration:

Set up this new PraxisJS project with me. Check that the praxisjs MCP server is connected, then read the praxisjs skill and the docs it points to. Run its session start checklist: ask me the setup questions in one message, set up the project memory, and confirm the folder structure and conventions with me before writing any code.

Existing project. praxisjs ai add, and praxisjs ai sync when it adds the MCP server, end with it. So does praxisjs add kosmesis when an AI integration is installed, so the new components get recorded:

Map this PraxisJS project into its memory. Check that the praxisjs MCP server is connected, then run the praxisjs-memory-bootstrap skill over the whole project. Confirm the folder structure and conventions you find with me, and ask about anything the code does not make clear.

The setup prompt runs the skill's first session: your preferences, then whether the project is still the empty scaffold (memory set up without an analysis) or should be mapped. The map prompt runs the praxisjs-memory-bootstrap skill directly, which analyzes the project in focused passes and writes .praxis/ through the MCP server. It is safe to run again later: entries are updated, not duplicated. See Project memory.


What's next?

  • Components — understand StatefulComponent vs StatelessComponent
  • Reactivity & Signals — how signals and arrow functions work under the hood
  • JSX Syntax — every JSX pattern: conditionals, lists, events, refs

On this page