Components, and where next
Extract a row into its own file, understand what isolation buys you, and find the edges of the current release.
The row has grown enough markup and CSS to deserve its own file. Put it in a
components/ folder next to your app:
components/row.rux
<template>
<view class="task" :class='#{ done: done }'>
<view class="box" />
<text class="label">{{ label }}</text>
</view>
</template>
<style>
.task {
display: flex;
flex-direction: row;
flex-grow: 1;
align-items: center;
gap: 10px;
padding: 12px;
background: #313244;
border-radius: 8px;
}
.box {
width: 18px;
height: 18px;
flex-shrink: 0;
background: #1e1e2e;
border: 2px #585b70 solid;
border-radius: 4px;
}
.label { color: #cdd6f4; font-size: 15px; }
.task.done .box { background: #a6e3a1; border: 2px #a6e3a1 solid; }
.task.done .label { color: #6c7086; text-decoration: line-through; }
</style>
Import it and use it as a tag. Props are the :-prefixed attributes, evaluated
in the caller's scope:
<script>
use components::row;
let draft = signal("");
let items = signal([ /* … */ ]);
</script><view class="slot" r-for="t in items"
@tap='for i in 0..items.len() { if items[i].label == t.label { items[i].done = !items[i].done; } }'>
<row :label="t.label" :done="t.done" />
</view>
use components::row; resolves to components/row.rux, relative to the file
doing the importing. It has to be alone on its own line: the import is
picked out of the script by a line scan, not parsed.
What isolation means
A component instance sees only its props. items and draft do not exist
inside row.rux, and its CSS styles its own subtree and nothing else.
That is why the tap handler stays in the parent, on a wrapper <view>: the
handler needs items, which the component cannot reach. The component receives
two plain values and decides how they look. It is a slightly awkward split here
and a very useful one as an app grows: a component can never quietly reach
into state it doesn't own.
Editing row.rux hot-reloads the running window just like the main file.
Checkpoint
examples/learn/05-components.rux, plus examples/learn/components/row.rux.
That is the whole language. Templates, CSS, signals, four directives, and components. There is no fifth concept waiting for you.
Try changing it
The most useful next step is to break it. A few that are worth the time:
- Delete a row.
items.remove(i)inside the same indexed loop. Watch out for the tap on the row firing at the same time. - Filter it. Add a
filtersignal and anr-ifon the row, or drive the list throughitems.filter(…)in ther-forexpression itself. - Persist it. This one needs Rust: register a
host::function and call it from a handler. That is the escape hatch for anything the script tier can't do. - Use a real checkbox.
<input type="checkbox" r-model="flag" />is a tap-toggle that writes its signal directly. A checked box carries a syntheticcheckedclass, so.box.checked { … }styles it.
Where the edges are
Rux is 0.x and honest about it. Things you will run into if you keep going,
as of v0.3.0:
- No CSS variables,
@media, or pseudo-classes. No:hover,:focusor:checked, which is why thecheckedclass above exists as a stopgap. These are the headline items of the next release. - No
fnthat mutates state, as chapter 3 covered. - No index in
r-for, and reordering a list re-renders more rows than a keyed diff would. - No effects or computed values. A
{{ }}expression is the only "computed" there is. - No true inline text flow, so no mixing bold into a sentence yet.
The reference has the exact honored-CSS set and the full gap list; the roadmap has the order they get fixed in, and the blog covers each release as it lands.
If you build something with this, or hit an edge that isn't written down,
the contribute page is the way in. It is a tour of
how a .rux file becomes pixels, and which crate owns which stage.