Skip to content
ONIWC
iwc 0.1.0

Tutorial 1: Create your first IWC project

Objective: create an IWC project, run it with live reload, understand every file the starter contains, and make your first change.

Prerequisites: IWC installed (iwc --version prints a version — see Installation). About 10 minutes.

Step 1: Create the project

Terminal
cd ~/Projects
iwc create hello --yes
cd hello

--yes skips the question about targets and creates a web app. Without it, iwc create asks whether you also want an admin area and Android or iOS apps.

Output
✓ Created hello from the basic template — targets: web

  cd hello
  iwc dev --open                 # web

Step 2: Look at what was created

Project files
hello/
├── assets/
│   └── logo.svg
├── components/
│   └── footer.component.iwc
├── layouts/
│   └── main.layout.iwc
├── state/
│   └── counter.state.iwc
├── styles/
│   └── main.css
├── tests/
│   ├── menu.test.iwc
│   └── smoke.test.iwc
├── ui/
│   ├── about.ui.iwc
│   ├── home.ui.iwc
│   ├── menu.ui.iwc
│   └── not-found.ui.iwc
├── app.iwc
├── package.iwc
└── README.md

The starter also writes .vscode/ (editor settings and JSON Schemas for autocompletion) and a .gitignore. The ui/menu.ui.iwc and tests/menu.test.iwc files are added in Tutorial 2.

app.iwc

The application file. It is the only file every project must have.

app.iwc
{
  "name": "Hello",
  "iwc": 1,
  "theme": {
    "tokens": {
      "color-primary": "#5b4ff5",
      "color-on-primary": "#ffffff",
      "color-text": "#14141f",
      "color-muted": "#5d6073",
      "color-background": "#ffffff",
      "color-surface": "#f6f6fb",
      "color-border": "#e6e6ef",
      "radius": "12px",
      "font-body": "Inter, system-ui, -apple-system, 'Segoe UI', sans-serif",
      "font-mono": "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
    },
    "dark": {
      "color-primary": "#8b7fff",
      "color-text": "#ececf4",
      "color-muted": "#a2a4b8",
      "color-background": "#0b0b13",
      "color-surface": "#15151f",
      "color-border": "#26263a"
    },
    "fonts": [
      { "family": "Inter", "google": true, "weights": [400, 500, 600, 700] }
    ]
  },
  "styles": ["styles/main.css"],
  "head": {
    "title": "%s · Hello",
    "description": "Hello, a new IWC application.",
    "favicon": "/assets/logo.svg"
  },
  "notFound": "not-found.ui.iwc",
  "platforms": {
    "web": {}
  }
}
PropertyWhat it does
nameThe application's name (shown in titles and native apps)
iwcThe file-format version
theme.tokensDesign tokens, emitted as CSS variables: color-primary becomes var(--color-primary)
theme.darkTokens that replace the defaults when the visitor's system is in dark mode
theme.fontsFonts to load; google: true loads them from Google Fonts
stylesYour stylesheets, in load order
head<title> template (%s is each screen's title), meta description, favicon
notFoundThe screen shown for unknown routes
platformsThe application targets: here, the web

ui/home.ui.iwc

A screen. route is its URL, layout the frame around it, and sections its content: a tree of elements.

ui/home.ui.iwc
{
  "name": "Home",
  "route": "/",
  "layout": "main.layout.iwc",
  "title": "Welcome",
  "sections": [
    {
      "type": "section",
      "className": "welcome",
      "elements": [
        {
          "type": "column",
          "className": "welcome-inner",
          "layout": { "direction": "column", "align": "center", "gap": "20px" },
          "elements": [
            { "type": "icon", "source": "/assets/logo.svg", "ariaLabel": "IWC", "className": "welcome-logo" },
            { "type": "text", "variant": "span", "content": "IWC v0.1.0", "className": "version" },
            { "type": "text", "variant": "title", "content": "Welcome to IWC" },
            {
              "type": "text",
              "className": "lead",
              "content": "Hello is a new IWC application. Its screens, state and theme are plain .iwc files, built for every platform in app.iwc."
            },
            {
              "type": "column",
              "className": "counter",
              "layout": { "direction": "column", "align": "center", "gap": "12px" },
              "elements": [
                { "type": "text", "variant": "caption", "content": "Shared behaviour: state/counter.state.iwc" },
                { "type": "text", "variant": "h2", "bind": "state.counter.count", "prefix": "Count: ", "className": "count" },
                {
                  "type": "row",
                  "layout": { "justify": "center", "align": "center", "gap": "12px" },
                  "elements": [
                    { "type": "button", "label": "Increment", "action": "state", "store": "counter", "mutation": "increment" },
                    { "type": "button", "label": "Reset", "variant": "secondary", "action": "state", "store": "counter", "mutation": "reset" }
                  ]
                }
              ]
            },
            {
              "type": "column",
              "className": "next-step",
              "layout": { "direction": "column", "align": "center", "gap": "8px" },
              "elements": [
                { "type": "text", "variant": "caption", "content": "Get started by editing" },
                { "type": "text", "variant": "span", "content": "ui/home.ui.iwc", "className": "file" },
                { "type": "text", "variant": "caption", "content": "Save the file and every running target rebuilds." }
              ]
            },
            {
              "type": "row",
              "className": "actions",
              "layout": { "justify": "center", "align": "center", "gap": "12px", "wrap": true },
              "elements": [
                { "type": "button", "label": "About this app", "variant": "secondary", "action": "navigate", "target": "/about" },
                { "type": "button", "label": "Read the documentation", "action": "navigate", "target": "/_iwc/docs/", "platforms": ["web"] }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Things to notice:

  • "type": "column" with "layout": {"direction": "column", "align": "center", "gap": "20px"} is a flexbox column.
  • "bind": "state.counter.count" shows a value from the client-side store in state/counter.state.iwc; the two buttons run its increment and reset mutations.
  • "platforms": ["web"] keeps the documentation button out of native apps.

state/counter.state.iwc

state/counter.state.iwc
{
  "name": "counter",
  "persist": "session",
  "initial": { "count": 0 },
  "mutations": {
    "increment": { "op": "increment", "path": "count" },
    "reset": { "op": "clear", "path": "count" }
  }
}

A browser-side store. persist: "session" keeps the count while the tab is open. See Client state.

layouts/main.layout.iwc and components/footer.component.iwc

layouts/main.layout.iwc
{
  "name": "MainLayout",
  "slots": {
    "content": "PAGE_CONTENT",
    "footer": "footer.component.iwc"
  },
  "className": "app-root"
}
components/footer.component.iwc
{
  "name": "Footer",
  "root": {
    "type": "row",
    "className": "app-footer",
    "layout": { "justify": "center", "align": "center", "gap": "6px", "wrap": true },
    "elements": [
      { "type": "text", "variant": "caption", "content": "Built with IWC" },
      { "type": "text", "variant": "caption", "content": "·", "className": "dot", "platforms": ["web"] },
      { "type": "link", "label": "Documentation", "target": "/_iwc/docs/", "platforms": ["web"] }
    ]
  }
}

The layout has two slots, rendered in order: the page's own content (PAGE_CONTENT) and a footer component. Every screen that says "layout": "main.layout.iwc" gets the same frame.

tests/smoke.test.iwc

tests/smoke.test.iwc
{
  "name": "smoke",
  "cases": [
    { "name": "home renders the welcome page", "request": { "path": "/" }, "expect": { "status": 200, "contains": ["Welcome to IWC", "ui/home.ui.iwc"] } },
    { "name": "home has the shared counter", "request": { "path": "/" }, "expect": { "status": 200, "contains": ["Increment", "Reset", "data-iwc-state=\"counter.count\""] } },
    { "name": "home links the about screen and the documentation", "request": { "path": "/" }, "expect": { "status": 200, "contains": ["href=\"/about\"", "/_iwc/docs/"] } },
    { "name": "the about screen renders", "request": { "path": "/about" }, "expect": { "status": 200, "contains": ["One app, every platform", "Back to home"] } },
    { "name": "the documentation is served", "request": { "path": "/_iwc/docs/getting-started" }, "expect": { "status": 200, "contains": ["iwc dev"] } },
    { "name": "unknown routes render the 404 screen", "request": { "path": "/no/such/page" }, "expect": { "status": 404, "contains": ["This page doesn"] } }
  ]
}

Each case sends a request to the app and checks the status and what the response contains.

Step 3: Run it

Terminal
iwc dev --open
Output
IWC Development Server

✓ Project validated
✓ Runtime initialized
✓ Port 3000 available
✓ Application compiled (9 files in 10ms · 3 screens · 0 apis · 0 schemas)
✓ Hot reload enabled

  Local:    http://localhost:3000
  Docs:     http://localhost:3000/_iwc/docs/
  Project:  /Users/you/Projects/hello
  Logs:     .iwc/logs/dev.log  (iwc logs -f)

Watching for file changes... (Ctrl+C stops)

Your browser opens the welcome page. Press Increment a few times, then open About this app: the count is shared between screens because it lives in the store.

Step 4: Change something

In ui/home.ui.iwc, change the title text:

element (in a .iwc file)
{ "type": "text", "variant": "title", "content": "Hello from my first IWC app" }

Save. The page updates in place, keeping its scroll position and the counter. Now make a mistake on purpose — change "variant": "title" to "variant": 12 — and save. The terminal shows the error with its position, and the page shows an overlay until you fix it.

Step 5: Check and test

Terminal
iwc check
iwc test
Output
✓ targets: web
✓ 9 files · 3 screens · 1 layouts · 1 components · 0 apis · 0 schemas · 0 actions

  ✓ smoke › home renders the welcome page
  ✓ smoke › home has the shared counter
  ✓ smoke › home links the about screen and the documentation
  ✓ smoke › the about screen renders
  ✓ smoke › the documentation is served
  ✓ smoke › unknown routes render the 404 screen

6 passed, 0 failed

If you changed the title in step 4, the first test now fails, because it expects the text "Welcome to IWC". That is the point of tests: update the expectation in tests/smoke.test.iwc to your new title.

Troubleshooting

ProblemFix
hello already existsPick another name or remove the folder.
The browser does not openOpen the Local: URL printed by iwc dev.
Port 3000 is already occupiediwc dev moves to the next free port and prints it.
Changes don't appearCheck the terminal for an error; the page shows an overlay while one exists.

Next: Build your first UI page.

Source: apps/marketing/content/docs/tutorials/first-project.md