Components
Components are the building blocks of Tova UIs. A component is a reactive function that declares state, computed values, and effects, and returns JSX describing what to render. Components are declared with the component keyword inside browser { } blocks.
Basic Components
The simplest component has no parameters and returns JSX:
browser {
component Hello {
<div>
<h1>Hello, World!</h1>
</div>
}
}The JSX at the end of the component body is the return value. You do not need an explicit return statement -- the last JSX element (or group of elements) is returned automatically.
Components with Props
Components accept props as parameters:
component Greeting(name) {
<div>
<h1>Hello, {name}!</h1>
</div>
}Multiple props are separated by commas:
component UserCard(name, email, avatar_url) {
<div class="card">
<img src={avatar_url} alt={name} />
<h2>{name}</h2>
<p>{email}</p>
</div>
}Default Prop Values
Props can have default values:
component Button(label, variant = "primary", disabled = false) {
<button class={variant} disabled={disabled}>
{label}
</button>
}
// Usage — variant defaults to "primary"
<Button label="Submit" />
<Button label="Cancel" variant="secondary" />Using Components
Components are used as JSX elements with an uppercase first letter:
component App {
<div>
<Greeting name="Alice" />
<Greeting name="Bob" />
<UserCard name="Carol" email="carol@example.com" avatar_url="/carol.png" />
</div>
}Self-closing syntax (<Component />) is used when there are no children. Components with children use the full open/close form:
component Layout(title) {
<div class="layout">
<header><h1>{title}</h1></header>
<main>{children}</main>
</div>
}
// Usage with children
<Layout title="My App">
<p>Page content goes here</p>
</Layout>Children are passed as a children prop and can be rendered in the component body.
Props Are Reactive Getters
Props in Tova are reactive getters, not plain values. When a parent's signal changes, the prop in the child component updates automatically:
component Parent {
state name = "Alice"
<div>
<ChildDisplay name={name} />
<button on:click={fn() name = "Bob"}>Change Name</button>
</div>
}
component ChildDisplay(name) {
// name is a reactive getter — it always returns the current value
// from the parent's signal
<p>Name: {name}</p>
}Under the hood, the compiler generates prop accessors as getter functions on the props object. When the parent writes:
<ChildDisplay name={name} />The generated code creates a props object with a getter:
ChildDisplay({ get name() { return name(); } })Inside the child component, name is a function () => __props.name that accesses the getter, maintaining reactivity through the component boundary.
Components with Local State
Components can declare their own reactive state:
component Counter {
state count = 0
<div>
<p>Count: {count}</p>
<button on:click={fn() count += 1}>+</button>
<button on:click={fn() count -= 1}>-</button>
</div>
}Local state is scoped to the component instance. Each instance of Counter has its own independent count signal.
Components with Computed Values
Components can declare computed values that derive from state or props:
component PriceDisplay(price, quantity) {
computed subtotal = price * quantity
computed tax = subtotal * 0.08
computed total = subtotal + tax
<div class="price-breakdown">
<p>Subtotal: ${subtotal}</p>
<p>Tax: ${tax}</p>
<p>Total: ${total}</p>
</div>
}Components with Effects
Components can include effects for side effects:
component PageTitle(title) {
effect {
document.title = title
}
<div>{title}</div>
}Effects declared inside a component are automatically disposed when the component unmounts.
Full Component Example
A component that combines state, computed values, effects, and event handling:
component TodoList {
state items = []
state new_text = ""
state filter = "all"
computed visible_items = match filter {
"active" => [t for t in items if not t.completed]
"completed" => [t for t in items if t.completed]
_ => items
}
computed remaining = len([t for t in items if not t.completed])
effect {
print("Todo count: {len(items)}, visible: {len(visible_items)}")
}
<div class="todo-app">
<h1>Todos</h1>
<form on:submit={fn(e) {
e.preventDefault()
if len(new_text) > 0 {
items = [...items, { id: len(items), text: new_text, completed: false }]
new_text = ""
}
}}>
<input bind:value={new_text} placeholder="What needs to be done?" />
<button type="submit">Add</button>
</form>
<div class="filters">
<button on:click={fn() filter = "all"} class:active={filter == "all"}>All</button>
<button on:click={fn() filter = "active"} class:active={filter == "active"}>Active</button>
<button on:click={fn() filter = "completed"} class:active={filter == "completed"}>Completed</button>
</div>
<ul>
for item in visible_items key={item.id} {
<li class:completed={item.completed}>
<input
type="checkbox"
checked={item.completed}
on:change={fn() {
items = [
if t.id == item.id { { ...t, completed: not t.completed } } else { t }
for t in items
]
}}
/>
<span>{item.text}</span>
</li>
}
</ul>
<p>{remaining} items remaining</p>
</div>
}Component Composition
Components compose naturally. Break complex UIs into smaller, reusable components:
component SearchInput(value, on_change, placeholder = "Search...") {
<input
type="search"
bind:value={value}
on:input={fn(e) on_change(e.target.value)}
placeholder={placeholder}
/>
}
component UserRow(user, on_select) {
<tr on:click={fn() on_select(user)}>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.role}</td>
</tr>
}
component UserTable(users, on_select) {
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
</tr>
</thead>
<tbody>
for user in users key={user.id} {
<UserRow user={user} on_select={on_select} />
}
</tbody>
</table>
}Event Callback Props
Components commonly accept callback props for communication with parents:
component Modal(title, on_close) {
<div class="modal-overlay" on:click={fn() on_close()}>
<div class="modal" on:click={fn(e) e.stopPropagation()}>
<header>
<h2>{title}</h2>
<button on:click={fn() on_close()}>X</button>
</header>
<div class="modal-body">{children}</div>
</div>
</div>
}
component App {
state show_modal = false
<div>
<button on:click={fn() show_modal = true}>Open Modal</button>
if show_modal {
<Modal title="Settings" on_close={fn() show_modal = false}>
<p>Modal content here</p>
</Modal>
}
</div>
}Scoped Slots
Components can pass data back to their parent through scoped slots using the <slot> element. This is useful for "render prop" patterns where a parent wants to control how a child renders its data.
Default Slot
The simplest slot renders the parent's children:
component Card(title) {
<div class="card">
<h2>{title}</h2>
<div class="card-body">
<slot />
</div>
</div>
}
// Usage
<Card title="User Info">
<p>This content goes into the slot</p>
</Card>Named Slots
Use name to define multiple named slots:
component Layout {
<div class="layout">
<header><slot name="header" /></header>
<main><slot /></main>
<footer><slot name="footer" /></footer>
</div>
}
// Usage — children with slot="name" go to named slots
<Layout>
<div slot="header"><h1>My App</h1></div>
<p>Main content</p>
<div slot="footer">Copyright 2024</div>
</Layout>Scoped Slots (Render Props)
Pass data from the child back to the parent by adding props to <slot>:
component DataList(items) {
<ul>
for item in items key={item.id} {
<li>
<slot item={item} index={index} />
</li>
}
</ul>
}
// Parent provides a render function as children
<DataList items={users}>
{fn(props) <span>{props.item.name} ({props.index})</span>}
</DataList>When <slot> has props, it checks if children is a function. If so, it calls the function with the slot props. Otherwise, it renders children normally.
The generated code for <slot item={item} index={index} /> is:
typeof __props.children === 'function'
? __props.children({ item: item, index: index })
: (__props.children || '')Auto-Mount
If a component named App exists, Tova automatically mounts it to the DOM when the page loads:
browser {
component App {
<div>
<h1>My Application</h1>
</div>
}
}The compiler generates:
document.addEventListener("DOMContentLoaded", () => {
mount(App, document.getElementById("app") || document.body);
});This looks for an element with id="app" in the HTML, falling back to document.body.
Under the Hood
When you write:
component Greeting(name) {
<h1>Hello, {name}!</h1>
}The compiler generates:
function Greeting(__props) {
const name = () => __props.name;
return tova_el("h1", {}, ["Hello, ", () => name()]);
}Components are plain functions that receive a props object and return virtual DOM nodes (vnodes). The reactive prop accessors ensure that when a parent signal changes, the child component's references to that prop automatically reflect the new value.