agentskills.codes
CO

control-flow

Reactive ranges for lists and conditionals — `For` (lists), `Show` (visibility), `When` (binary branches), `Switch` / `Match` (multi-way). Load this when the task is about rendering a list of items, conditionally showing/hiding elements, or pattern-matching on a value to render different branches.

Install

mkdir -p .claude/skills/control-flow-yw662 && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16055" && unzip -o skill.zip -d .claude/skills/control-flow-yw662 && rm skill.zip

Installs to .claude/skills/control-flow-yw662

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Reactive ranges for lists and conditionals — `For` (lists), `Show` (visibility), `When` (binary branches), `Switch` / `Match` (multi-way). Load this when the task is about rendering a list of items, conditionally showing/hiding elements, or pattern-matching on a value to render different branches.
298 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)

About this skill

Control Flow

rikka-dom ships four reactive range helpers: For for lists, Show for visibility, When for binary branches, Switch / Match for multi-way. All cache their DOM so toggling does not rebuild elements.

Imports

import { For, Show, When, Switch, Match } from "@takanashi/rikka-dom";

For(source, render, keyFn?): ReactiveRange

Render a list from a Signal.State<T[]> or Signal.Computed<T[]>. Returns a range you can mount anywhere Child is accepted.

import { signal } from "@takanashi/rikka-signal";
import { For, li } from "@takanashi/rikka-dom";

const items = signal(["a", "b", "c"]);
const list = For(items, (item) => li({}, item));

Keyed rendering

Pass a keyFn to cache DOM by key, so updates only add/remove the changed items:

const users = signal([{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]);

For(
  users,
  (user) => li({}, user.name),
  (user) => user.id, // keyFn
);

Without a keyFn, caching is by reference (Object.is). Unused cached entries are cleaned up on each re-evaluation.

Show(condition, render): ReactiveRange

Toggle visibility based on a signal. The element is created once and shown/hidden on subsequent toggles.

import { signal } from "@takanashi/rikka-signal";
import { Show, span } from "@takanashi/rikka-dom";

const visible = signal(true);

Show(visible, () => span({}, "visible"));
// visible.set(false) → element removed from DOM
// visible.set(true)  → same element re-inserted

When(condition, trueRender, falseRender): ReactiveRange

Two-branch conditional. Both branches are cached.

import { signal } from "@takanashi/rikka-signal";
import { When, span } from "@takanashi/rikka-dom";

const loggedIn = signal(false);

When(
  loggedIn,
  () => span({}, "Welcome!"),
  () => span({}, "Please log in"),
);

Switch(value, ...cases) and Match(matcher, render)

Multi-way matching. Each case DOM is cached. The first matching case is shown.

import { signal } from "@takanashi/rikka-signal";
import { Switch, Match, textarea, div } from "@takanashi/rikka-dom";

const mode = signal("edit");

Switch(
  mode,
  Match("edit", () => textarea()),
  Match("preview", () => div({}, "Preview")),
  Match(
    (v) => v.startsWith("admin"),  // predicate form
    () => div({}, "Admin"),
  ),
);

Match's matcher is either:

  • A value — compared with Object.is
  • A predicate function (value: T) => boolean

The last case acts as a fallback (no matcher, or always-true predicate).

Nesting control flow

For / Show / When / Switch return a ReactiveRange, which is a valid Child. Compose freely:

For(todos, (todo) =>
  Show(todo.done, () => s({}, todo.text), () => s({}, todo.text, " ✓"))
);

Pitfalls

1. Plain array instead of signal

// ❌ Static — list never updates
For(["a", "b", "c"] as any, (x) => li({}, x));

// ✅ Signal — list updates
For(signal(["a", "b", "c"]), (x) => li({}, x));

(rikka's type system catches this — source must be a Signal.State<T[]> or Signal.Computed<T[]>.)

2. Forgetting keyFn on a list with stable IDs

Without keyFn, caching is by reference. If your data is replaced wholesale (e.g. fetched fresh from an API), every item will be re-rendered. Use keyFn whenever your items have a stable identity:

// Without keyFn: full re-render on each refresh
For(users, (u) => li({}, u.name));

// With keyFn: only changed items re-render
For(users, (u) => li({}, u.name), (u) => u.id);

3. Forgetting () after () => in Match

Switch(mode,
  Match("edit", () => textarea()),  // ✅ render function called
  Match("preview", textarea),       // ❌ passes the function reference, not its result
);

Always invoke the render function with ().

4. Using Show for two branches

// ❌ Awkward — two `Show` blocks
Show(cond, () => A());
Show(computed(() => !cond.get()), () => B());

// ✅ Use `When`
When(cond, () => A(), () => B());

See also

Search skills

Search the agent skills registry