Tutorial 2: Build your first UI page
Objective: add a new screen at /menu that lists dishes in a responsive grid with formatted prices, and cover it with a test.
Prerequisites: the hello project from Tutorial 1, with iwc dev running.
Step 1: Create the screen
Create ui/menu.ui.iwc. The file name is yours; the .ui.iwc extension makes it a screen, and route decides its URL. No registration is needed.
{
"name": "Menu",
"route": "/menu",
"layout": "main.layout.iwc",
"title": "Our menu",
"description": "Seasonal bowls and slow coffee, served all day.",
"sections": [
{
"type": "section",
"className": "menu",
"layout": { "direction": "column", "gap": "24px" },
"elements": [
{ "type": "text", "variant": "eyebrow", "content": "Served 8am – 10pm" },
{ "type": "text", "variant": "h1", "content": "Our menu" },
{
"type": "text",
"className": "lead",
"content": "Everything is made in our kitchen from seasonal produce. Ask us about allergens."
},
{
"type": "grid",
"columns": 3,
"responsiveColumns": { "tablet": 2, "mobile": 1 },
"gap": "16px",
"data": {
"source": "STATIC",
"items": [
{ "name": "Green Goddess Bowl", "price": 349, "note": "Avocado, chickpeas, greens" },
{ "name": "Smoky Paneer Wrap", "price": 279, "note": "Charred paneer, mint chutney" },
{ "name": "Cold Brew", "price": 189, "note": "Steeped for 18 hours" }
]
},
"item": {
"type": "column",
"className": "dish",
"layout": { "direction": "column", "gap": "6px" },
"elements": [
{ "type": "text", "variant": "h3", "bind": "name" },
{ "type": "text", "variant": "caption", "bind": "note" },
{ "type": "text", "variant": "label", "bind": "price", "format": "currency", "currency": "INR" }
]
}
},
{ "type": "button", "label": "Back to home", "variant": "secondary", "action": "navigate", "target": "/" }
]
}
]
}Open http://localhost:3000/menu.
Step 2: Understand the screen
| Property | Meaning |
|---|---|
name | A name for tooling; also the page title when title is missing |
route | The URL. /menu/:slug would capture a parameter |
layout | Wraps the screen in main.layout.iwc (the footer from Tutorial 1) |
title, description | The <title> (through head.title, so "Our menu · Hello") and the meta description |
sections | The content, top to bottom |
Inside the section:
- Text variants choose the HTML tag:
eyebrowis a small label above a heading,h1the page heading, andparagraph(the default) a<p>. See the text element. - The grid has 3 columns, 2 below the
tabletbreakpoint (1024px) and 1 belowmobile(640px). data+itemmake the grid data-driven:STATICreads theitemswritten in the file, anditemis repeated once per record. Later tutorials useDATABASE.andAPI.sources in exactly the same place.bindputs a record's field into an element.format: "currency"withcurrency: "INR"turns349into₹349.00.
Step 3: Style it
Append to styles/main.css:
/* --------------------------------------------------------------- menu */
.menu { width: 100%; max-width: 960px; margin: 0 auto; padding: 48px 16px; }
.dish { padding: 20px; border: 1px solid var(--color-border); border-radius: var(--radius); background: var(--color-surface); }
.dish h3 { margin: 0; }The page restyles in place as you save. var(--color-border) and friends come from the theme tokens in app.iwc, including their dark-mode values.
Step 4: Add a test
Create tests/menu.test.iwc:
{
"name": "menu",
"cases": [
{
"name": "the menu lists every dish with its price",
"request": { "path": "/menu" },
"expect": { "status": 200, "contains": ["Our menu", "Green Goddess Bowl", "Cold Brew", "₹349.00"] }
}
]
}iwc test ✓ menu › the menu lists every dish with its price
✓ smoke › home renders the welcome page
…
7 passed, 0 failedStep 5: Link to it
To link the page from the home screen, add a button to the actions row in ui/home.ui.iwc:
{ "type": "button", "label": "See the menu", "variant": "secondary", "action": "navigate", "target": "/menu" }IWC checks links: a target route that no screen has is reported as a warning by iwc check.
Troubleshooting
| Message | Fix |
|---|---|
a list data source needs an "item" element to repeat | A container with data needs item. |
text elements need "content" or "bind" | Each text element shows literal content or a bound field. |
| Prices show as plain numbers | Add "format": "currency" and a currency code. |
Next: Create a reusable button.
Source: apps/marketing/content/docs/tutorials/first-page.md