Checklist
Done-state lives in a per-task signal, and the row binds a style class to it with bindClass. Classes cascade: $done set on the button is active on all of its children too — the circle, the checkmark and the title each declare their own $done block with its own transition, so the row restyles in place, no remounts. The checkmark itself is a separate SVG file in the project: importing it yields an icon you recolor with tintColor.
// Rows never remount: done-state lives in a per-task signal and each row binds a style class to
// it. Classes cascade — `$done` set on the button is also active on all of its children — so the
// circle, the checkmark and the title each declare their own `$done` block, each with its own
// transition. The checkmark is an SVG file beside this one; importing it yields a tintable icon.
import check from "./check.svg"
type Task = { title: string, done: Signal<boolean> }
const tasks: Task[] = [
{ title: "Read the conventions", done: signal(true) },
{ title: "Fork an example", done: signal(false) },
{ title: "Run it on a phone", done: signal(false) },
{ title: "Ship something small", done: signal(false) },
]
const left = computed(() => tasks.filter(t => !t.done.value).length)
const Row = (task: Task) => UIButton([
UIColumn([
UIImage(check).style({
width: 12,
height: 12,
tintColor: "white",
opacity: 0,
$done: { opacity: 1, duration: 150 }
}),
]).style({
width: 22,
height: 22,
borderRadius: 11,
border: "2px solid #39404f",
justifyContent: "center",
alignItems: "center",
$done: { bgColor: "#22c55e", borderColor: "#22c55e", duration: 150 }
}),
UIText(task.title).style({
fontSize: 16,
color: "white",
$done: { color: "#5c6272", textDecoration: "line-through", duration: 150 }
}),
]).style({ height: 58, px: 16, gap: 14, borderRadius: 14, bgColor: "#151922",
justifyContent: "flex-start",
// $pressed is toggled by the system, and cascades exactly like a class of your own.
$pressed: { transform: "scale(0.98)", duration: 100 } })
.class({ done: () => task.done.value })
.onClick(() => task.done.value = !task.done.value)
const screen = UIScreen([
UIText("Today").style({ fontSize: 30, fontWeight: 700, color: "white" }),
UIText(() => `${left.value} left`).style({ fontSize: 14, color: "#8b90a0", mb: 6 }),
UIScrollable(tasks.map(Row)).style({ flexGrow: 1, gap: 8 }),
]).style({ bgColor: "#0b0d12", px: 16, pt: "max(safe-top, 24px)", pb: "comfort-bottom" })
screen.open()