View Syntax
February 7, 2026 · View on GitHub
Coi uses a JSX-like syntax for defining component views. This guide covers HTML elements, expressions, conditional rendering, and loops.
Basic Elements
view {
<div class="container">
<h1>Hello World</h1>
<p>Welcome to Coi</p>
</div>
}
Expressions
Use curly braces {} to embed expressions:
component Greeting {
string name = "World";
int count = 42;
view {
<div>
<h1>Hello, {name}!</h1>
<p>Count: {count}</p>
<p>Double: {count * 2}</p>
</div>
}
}
Raw HTML
For rendering HTML strings (e.g., from a CMS or markdown parser), use the <raw> element:
component Article {
string htmlContent = "<p>This is <strong>bold</strong> text.</p>";
view {
<div class="article">
<raw>{htmlContent}</raw>
</div>
}
}
Security Warning: The <raw> element renders HTML without sanitization. Only use it with trusted content. Never pass user input directly to <raw> without sanitizing it first.
// ✗ Dangerous - XSS vulnerability
<raw>{userInput}</raw>
// ✓ Safe - sanitized or trusted content only
<raw>{sanitizedMarkdown}</raw>
<raw>{trustedCmsContent}</raw>
Dynamic Updates: Like other view elements, <raw> content updates reactively when dependencies change:
component Preview {
mut string markdown = "# Hello";
mut string rendered = "<h1>Hello</h1>";
def updatePreview(string value) : void {
markdown = value;
rendered = markdownToHtml(value); // Your markdown parser
}
view {
<div>
<textarea oninput={updatePreview}>{markdown}</textarea>
<div class="preview">
<raw>{rendered}</raw>
</div>
</div>
}
}
Event Handlers
Bind methods to events with on<event>:
Click Events
component Button {
mut int clicks = 0;
def handleClick() : void {
clicks += 1;
}
view {
<button onclick={handleClick}>Clicked {clicks} times</button>
}
}
Input Events
For oninput and onchange, the handler receives the input's current value as a string:
component SearchBox {
mut string query = "";
def handleInput(string value) : void {
query = value;
}
view {
<input
type="text"
value={query}
oninput={handleInput}
/>
}
}
Keyboard Events
For onkeydown, the handler receives the key code as an int:
component KeyboardInput {
mut string lastKey = "";
def handleKeyDown(int keycode) : void {
if (keycode == 13) {
lastKey = "Enter";
} else if (keycode == 27) {
lastKey = "Escape";
}
}
view {
<input onkeydown={handleKeyDown} />
}
}
Event Handler Summary
| Event | Handler Signature | Description |
|---|---|---|
onclick | def handler() : void | Mouse click |
oninput | def handler(string value) : void | Input value changed |
onchange | def handler(string value) : void | Input lost focus after change |
onkeydown | def handler(int keycode) : void | Key pressed |
Element References
Bind DOM elements to variables with &=:
component CanvasApp {
mut Canvas canvas;
mount {
canvas.setSize(800, 600);
}
view {
<canvas &={canvas}></canvas>
}
}
Conditional Rendering
Basic <if>
view {
<div>
<if showContent>
<p>Content is visible!</p>
</if>
</div>
}
<if> with <else>
view {
<div>
<if status == "active">
<span class="green">Active</span>
<else>
<span class="red">Inactive</span>
</else>
</if>
</div>
}
Nested Conditions
view {
<div>
<if score >= 90>
<span>A</span>
<else>
<if score >= 80>
<span>B</span>
<else>
<span>C</span>
</else>
</if>
</else>
</if>
</div>
}
List Rendering
Range-based Loop
view {
<div class="list">
<for i in 0:itemCount>
<div class="item">Item {i}</div>
</for>
</div>
}
Array Loop with Key
Array loops require a key attribute for efficient updates:
component TodoList {
mut TodoItem[] todos;
view {
<div>
<for todo in todos key={todo.id}>
<{todo} />
</for>
</div>
}
}
When the array changes:
- Items with the same key are reused (not recreated)
- New keys trigger item creation
- Removed keys trigger item destruction
- Reordering moves existing DOM nodes
Nested Loops
view {
<div class="grid">
<for row in 0:3>
<for col in 0:3>
<div class="cell">{row},{col}</div>
</for>
</for>
</div>
}
Child Components
Component names must start with an uppercase letter to distinguish them from HTML elements.
Basic Usage
component App {
view {
<div>
<Header title="My App" />
<Content />
<Footer />
</div>
}
}
Passing Props
// Value props (copied)
<Button label="Click me" size={24} />
// Reference props (two-way binding with &)
<Counter &count={score} />
// Move props (transfer ownership with :)
<Consumer :data={text} />
// Callback props
<ListItem &onRemove={handleRemove} />
Prop Passing Summary
| Syntax | Mode | Description |
|---|---|---|
prop={value} | Copy | Value is copied to child |
&prop={value} | Reference | Child can modify parent's value |
:prop={value} | Move | Ownership transferred to child |
Component Member References
Components declared as members can be rendered using <{member}/> syntax:
component App {
mut Editor editor;
view {
<div>
<{editor} /> // Renders the editor member
</div>
}
}
This is especially useful in loops where each item is a component:
component TodoList {
mut TodoItem[] todos;
view {
<for todo in todos key={todo.id}>
<{todo} &onRemove={removeTodo(todo.id)} />
</for>
}
}
With <{todo}/>, props like id, text, and done are automatically bound from the component instance. You only need to pass additional props like callbacks.
Dynamic Styles
Embed expressions in style attributes:
component Ball {
mut float x = 100;
mut float y = 100;
string color = "#4285f4";
view {
<div
class="ball"
style="left: {x}px; top: {y}px; background: {color};"
></div>
}
}
Next Steps
- Styling — Scoped and global CSS
- Components — Component lifecycle, state management
- Platform APIs — Canvas, Storage, Audio, and more