rEFui
Overview
Apply rEFui’s retained-mode + signals model correctly, choose the right JSX mode/renderer, and fix reactivity/lifecycle issues without importing patterns from other UI frameworks.
General guide
Mental model (retained mode)
- Component bodies are setup: they run once; they do not “re-render”.
- JSX is evaluated once; signals update the already-built UI incrementally.
- If something “doesn’t update”, you almost always read
.value too early (non-reactively) or mutated in place without trigger().
Signals & reactivity
- State:
const count = signal(0)
- Reactive JSX:
{count} (not {count.value})
- Derived:
const label = $(() => Count: ${count.value}) and then {label}
- In-place mutation: call
sig.trigger() after mutating arrays/objects.
import { signal, $ } from 'refui'
const Counter = () => {
const count = signal(0)
return (
<button on:click={() => count.set(count.value + 1)}>
{$(() => `Count: ${count.value}`)}
</button>
)
}
Effects & cleanup
- Reactive effect:
watch(() => { ...reads signals... })
- Setup/cleanup:
useEffect(() => { ...; return () => cleanup })
- Teardown-only:
onDispose(() => cleanup)
- Scheduling: if you need “after updates applied”,
await nextTick().
Control flow components
- Conditional UI:
<If condition={cond}>{() => <Then />}{() => <Else />}</If>
- Lists:
<For entries={items} track="id">{({ item, index }) => ...}</For>
- Inline dynamic subtree with lifecycle:
<Fn ctx={something}>{(ctx) => ...}</Fn>
For has no fallback; for empty states, wrap with <If>.
import { signal, $, If, For } from 'refui'
const App = () => {
const items = signal([{ id: 1, name: 'A' }])
return (
<If condition={$(() => items.value.length)}>
{() => <For entries={items} track="id">{({ item }) => <div>{item.name}</div>}</For>}
{() => <div>Empty</div>}
</If>
)
}
Async UI
<Async> for a single promise boundary.
<Suspense> to group multiple async subtrees under one fallback.
lazy(() => import(...)) for code-splitting; pair with fallback boundaries.
See references/async-suspense-transition.md for the “rules of engagement”.
Context
Use context for shared subtree values. If consumers must react to changes, provide a signal as the context value.
import { signal, $, createContext, useContext } from 'refui'
const Theme = createContext(signal('light'), 'Theme')
const Button = () => {
const theme = useContext(Theme)
return <button class:dark={$(() => theme.value === 'dark')}>OK</button>
}
Non-Reflow/custom renderer: wrap Provider children in a function so they inherit context: <Theme value={x}>{() => <Button />}</Theme>.
Mounting (DOM / HTML)
- DOM renderer (browser):
createDOMRenderer(defaults).render(target, App)
- HTML renderer (SSR/SSG):
createHTMLRenderer().serialize(createElement(App, props))
- JSX + renderer selection: see
references/jsx-and-renderers.md (automatic vs classic transform).
import { createDOMRenderer } from 'refui/dom'
import { defaults } from 'refui/browser'
createDOMRenderer(defaults).render(document.getElementById('app'), App)
DOM directives (browser preset)
- Events:
on:click={fn} (+ on-once:*, on-passive:*, on-capture:*)
- Classes/styles:
class:active={boolOrSignal}, style:color={valueOrSignal}
- Attributes vs props:
attr:* (SVG/read-only), prop:* (force property write)
- Macros:
m:* for reusable DOM behaviors (renderer-registered handlers)
Default Policy: Use rEFui Built-ins First
When implementing a requirement, prefer rEFui’s built-in primitives (signals/components/extras/renderers) over custom plumbing. Only fall back to a custom implementation when:
- rEFui has no built-in primitive that matches the requirement, and
- the project’s rEFui version lacks an equivalent helper, and
- you can’t express it cleanly as a DOM macro (
m:*) or small reusable component.
Before writing code, consult references/dos-and-donts.md to avoid React-style mistakes.
Quick Triage (do this first)
- Identify JSX mode in the target repo:
- Automatic runtime: look for
jsx: 'automatic' + jsxImportSource: 'refui' (Vite/esbuild) or jsxImportSource: "refui" (tsconfig/Bun).
- Classic transform: look for
jsxFactory: 'R.c' + jsxFragment: 'R.f' (Vite/esbuild) or /** @jsx R.c */ file pragmas.
- Identify the host renderer:
- Browser apps:
createDOMRenderer(defaults) from refui/dom + refui/browser (or refui/presets/browser in older repos).
- SSR/SSG:
createHTMLRenderer() from refui/html, then serialize().
- Reflow logic-only modules:
refui/reflow (often injected via jsxInject: import { R } from 'refui/reflow' in classic mode).
- Confirm rEFui version (it changes API details across repos). Prefer the repo’s local docs or installed
refui exports.
If you want an automated scan for JSX mode + common pitfalls, run node scripts/refui-audit.mjs <path> from inside this skill folder.
When Usage Is Unclear (consult MCP docs)
If you are unsure about a rEFui API, behavior, or best practice and cannot inspect the library source:
- Use Context7 MCP to pull authoritative, up-to-date library docs/snippets:
- First resolve the library:
mcp__context7__resolve-library-id with libraryName: "refui".
- Then query:
mcp__context7__query-docs for the specific API/task (e.g. “For track vs indexed”, “createDOMRenderer macros”, “nextTick vs tick semantics”, “classic vs automatic JSX setup”).
- Use DeepWiki MCP for repository-level questions (when the upstream repo is available):
mcp__deepwiki__read_wiki_structure then mcp__deepwiki__ask_question on SudoMaker/rEFui for conceptual/system questions or “where is X documented?”.
If MCP docs still leave ambiguity, ask the user for: the refui version, their bundler config (Vite/esbuild/Bun/TS/Babel), and a minimal repro snippet.
“Which rEFui feature should I use?” (fast mapping)
Use these references when choosing a built-in solution:
- Idiot-proof do/don’t checklist:
references/dos-and-donts.md
- Async UI:
references/async-suspense-transition.md
- Overlays/teleports/rich HTML/custom elements:
references/portals-parse-custom-elements.md
- Lists/identity/caching/perf:
references/lists-cache-memo.md
- Project setup:
references/project-setup.md
Non-Negotiables (retained mode)
- Do not write React/Vue/Solid/Svelte primitives (
useState, hooks, VDOM assumptions, $: blocks, etc.). Map them to rEFui signals/effects.
- Treat component bodies as setup (constructor-ish). JSX is evaluated once; signals drive incremental updates afterward.
- Keep reactive reads reactive:
- ✅ Use a signal directly:
<div>{count}</div>
- ✅ Wrap derived expressions:
<div>{$(() => Count: ${count.value})}</div> or <div>{computed(() => ...)}</div>
- ❌ Avoid inline
.value in JSX: <div>{count.value}</div> (evaluates once, won’t update)
- Remember scheduling: signal effects/computed flush at the end of the tick; use
await nextTick() when you must observe derived updates.
Hard Rules (idiot-proof guardrails)
- This is not React. Component bodies run once; JSX does not re-run. Do not expect re-renders.
- Do not invent props. If the API is unclear, open the .d.ts or use MCP. Example:
For has no fallback prop.
If / For / templates accept a single renderable. If you need multiple nodes, wrap them in a container or fragment.
For empty state: wrap it in If and provide a false branch. Example:
<If condition={$(() => items.value.length)}><For entries={items} track="id">{({ item }) => <Row item={item} />}</For><Empty /></If>
- Use
.value inside computed / $(() => ...) / watch / event handlers, not directly in JSX text/attrs.
Default Patterns (copy these mentally)
- State:
const x = signal(initial)
- Derived:
const y = $(() => /* uses x.value */) (or computed(() => ...))
- Effects:
watch(fn) for reactive computations; useEffect(setup) for setup+cleanup; onDispose(cleanup) for teardown.
- Lists:
- Keyed:
<For entries={items} track="id">{({ item }) => ...}</For>
- Unkeyed (perf experiments / reorder heavy):
UnKeyed from refui/extras/unkeyed.js
- If mutating arrays/objects in place: call
sig.trigger() after mutation.
- Async:
<Async future={promise} fallback={...} catch={...}>{({ result }) => ...}</Async>
<Suspense> for grouping async subtrees
async components are supported; pair with fallbacks when needed.
- DOM directives/events (DOM renderer):
- Events:
on:click={...}, plus options on-once:*, on-passive:*, on-capture:*
- Attributes vs props: prefer
attr: for SVG or when a DOM prop is read-only; use prop: to force a property set.
- Preset directives (browser preset):
class:x={boolSignal}, style:color={valueOrSignal}
- Macros:
m:name={value} where name is registered on the renderer.
- Refs/handles:
$ref={sig} to receive a node/instance in sig.value
$ref={(node) => ...} callback form
- Prefer
expose prop for imperative child handles (v0.8.0+).
Workflows
Fix “UI not updating”
- Search for JSX
{something.value} and decide if it must be reactive:
- Replace with
{something} when something is already a signal.
- Wrap derived text/attrs with
$(() => ...) / computed(() => ...) / t\...``.
- If you mutated an object/array held by a signal in-place, add
sig.trigger() (or replace with a new object/array).
- If you read derived values immediately after writes, insert
await nextTick() before reading computed/DOM-dependent values.
- If an effect runs “forever”, ensure it’s created inside a component scope and cleaned up via
useEffect/onDispose.
Add a feature safely
- Keep renderer creation at the entry point; do not create renderers inside components.
- Localize state: prefer per-component signals over global blobs; use
extract/derivedExtract to reduce fan-out.
- For repeated DOM behaviors, register a macro and use it via
m:* rather than duplicating manual DOM code.
- For lists, choose keyed
<For> unless you have a measured reason to use unkeyed.
Set up a new project (when asked)
- Ask only: preferred package manager (
npm/pnpm/yarn/bun) and language (JS/TS). Do not ask runtime.
- Default to JSX automatic runtime + JavaScript +
refui latest from npm unless the user specifies otherwise.
- Follow
references/project-setup.md.
Repo Navigation (load only if needed)
Read these files when you need deeper details:
references/project-triage.md for determining JSX mode/renderer/version from a project that uses rEFui as a dependency.
references/project-setup.md for scaffolding a new project (default: JSX automatic runtime + pure JS).
references/jsx-and-renderers.md for choosing classic vs automatic and DOM/HTML/Reflow specifics.
references/reactivity-pitfalls.md for high-signal debugging checklists and anti-patterns.
references/dos-and-donts.md for per-API/component do’s and don’ts that prevent React-mindset mistakes.
references/async-suspense-transition.md for <Async>, <Suspense>, lazy, and Transition.
references/portals-parse-custom-elements.md for portals/teleports, HTML parsing, and custom elements.
references/lists-cache-memo.md for <For>, identity, UnKeyed, caching, and memoization.
Resources
scripts/
scripts/refui-audit.mjs: quick scan for JSX mode + common .value-in-JSX pitfalls.
references/
references/project-triage.md
references/jsx-and-renderers.md
references/reactivity-pitfalls.md
references/dos-and-donts.md
references/async-suspense-transition.md
references/portals-parse-custom-elements.md
references/lists-cache-memo.md
references/project-setup.md
1---2name: refui3description: Use when working with rEFui (refui) applications where you cannot rely on reading the library source. Covers the retained-mode + signals mental model, DOM/HTML/Reflow renderers, JSX setup (classic pragma vs automatic runtime), directives (on:/class:/style:/attr:/prop:/m: macros), HMR via refurbish/refui/hmr, debugging “UI not updating” issues, and migrating React/Vue/Solid/Svelte patterns to rEFui.4---5
6# rEFui
7
8## Overview
9
10Apply rEFui’s retained-mode + signals model correctly, choose the right JSX mode/renderer, and fix reactivity/lifecycle issues without importing patterns from other UI frameworks.
11
12## General guide
13
14### Mental model (retained mode)
15
16- Component bodies are **setup**: they run once; they do not “re-render”.
17- JSX is evaluated once; **signals** update the already-built UI incrementally.
18- If something “doesn’t update”, you almost always read `.value` too early (non-reactively) or mutated in place without `trigger()`.
19
20### Signals & reactivity
21
22- State: `const count = signal(0)`
23- Reactive JSX: `{count}` (not `{count.value}`)
24- Derived: `const label = $(() => `Count: ${count.value}`)` and then `{label}`
25- In-place mutation: call `sig.trigger()` after mutating arrays/objects.
26
27```jsx
28import { signal, $ } from 'refui'
29
30const Counter = () => {
31 const count = signal(0)
32 return (
33 <button on:click={() => count.set(count.value + 1)}>
34 {$(() => `Count: ${count.value}`)}
35 </button>
36 )
37}
38```
39
40### Effects & cleanup
41
42- Reactive effect: `watch(() => { ...reads signals... })`
43- Setup/cleanup: `useEffect(() => { ...; return () => cleanup })`
44- Teardown-only: `onDispose(() => cleanup)`
45- Scheduling: if you need “after updates applied”, `await nextTick()`.
46
47### Control flow components
48
49- Conditional UI: `<If condition={cond}>{() => <Then />}{() => <Else />}</If>`
50- Lists: `<For entries={items} track="id">{({ item, index }) => ...}</For>`
51- Inline dynamic subtree with lifecycle: `<Fn ctx={something}>{(ctx) => ...}</Fn>`
52- `For` has **no** `fallback`; for empty states, wrap with `<If>`.
53
54```jsx
55import { signal, $, If, For } from 'refui'
56
57const App = () => {
58 const items = signal([{ id: 1, name: 'A' }])
59 return (
60 <If condition={$(() => items.value.length)}>
61 {() => <For entries={items} track="id">{({ item }) => <div>{item.name}</div>}</For>}
62 {() => <div>Empty</div>}
63 </If>
64 )
65}
66```
67
68### Async UI
69
70- `<Async>` for a single promise boundary.
71- `<Suspense>` to group multiple async subtrees under one fallback.
72- `lazy(() => import(...))` for code-splitting; pair with fallback boundaries.
73See `references/async-suspense-transition.md` for the “rules of engagement”.
74
75### Context
76
77Use context for shared subtree values. If consumers must react to changes, provide a signal as the context value.
78
79```jsx
80import { signal, $, createContext, useContext } from 'refui'
81
82const Theme = createContext(signal('light'), 'Theme')
83
84const Button = () => {
85 const theme = useContext(Theme)
86 return <button class:dark={$(() => theme.value === 'dark')}>OK</button>
87}
88```
89
90Non-Reflow/custom renderer: wrap Provider children in a function so they inherit context: `<Theme value={x}>{() => <Button />}</Theme>`.
91
92### Mounting (DOM / HTML)
93
94- DOM renderer (browser): `createDOMRenderer(defaults).render(target, App)`
95- HTML renderer (SSR/SSG): `createHTMLRenderer().serialize(createElement(App, props))`
96- JSX + renderer selection: see `references/jsx-and-renderers.md` (automatic vs classic transform).
97
98```jsx
99import { createDOMRenderer } from 'refui/dom'
100import { defaults } from 'refui/browser'
101
102createDOMRenderer(defaults).render(document.getElementById('app'), App)
103```
104
105### DOM directives (browser preset)
106
107- Events: `on:click={fn}` (+ `on-once:*`, `on-passive:*`, `on-capture:*`)
108- Classes/styles: `class:active={boolOrSignal}`, `style:color={valueOrSignal}`
109- Attributes vs props: `attr:*` (SVG/read-only), `prop:*` (force property write)
110- Macros: `m:*` for reusable DOM behaviors (renderer-registered handlers)
111
112## Default Policy: Use rEFui Built-ins First
113
114When implementing a requirement, prefer rEFui’s built-in primitives (signals/components/extras/renderers) over custom plumbing. Only fall back to a custom implementation when:
115- rEFui has no built-in primitive that matches the requirement, and
116- the project’s rEFui version lacks an equivalent helper, and
117- you can’t express it cleanly as a DOM macro (`m:*`) or small reusable component.
118
119Before writing code, consult `references/dos-and-donts.md` to avoid React-style mistakes.
120
121## Quick Triage (do this first)
122
1231. Identify **JSX mode** in the target repo:
124 - **Automatic runtime**: look for `jsx: 'automatic'` + `jsxImportSource: 'refui'` (Vite/esbuild) or `jsxImportSource: "refui"` (tsconfig/Bun).
125 - **Classic transform**: look for `jsxFactory: 'R.c'` + `jsxFragment: 'R.f'` (Vite/esbuild) or `/** @jsx R.c */` file pragmas.
1262. Identify the **host renderer**:
127 - Browser apps: `createDOMRenderer(defaults)` from `refui/dom` + `refui/browser` (or `refui/presets/browser` in older repos).
128 - SSR/SSG: `createHTMLRenderer()` from `refui/html`, then `serialize()`.
129 - Reflow logic-only modules: `refui/reflow` (often injected via `jsxInject: import { R } from 'refui/reflow'` in classic mode).
1303. Confirm **rEFui version** (it changes API details across repos). Prefer the repo’s local docs or installed `refui` exports.
131
132If you want an automated scan for JSX mode + common pitfalls, run `node scripts/refui-audit.mjs <path>` from inside this skill folder.
133
134## When Usage Is Unclear (consult MCP docs)
135
136If you are unsure about a rEFui API, behavior, or best practice and cannot inspect the library source:
137
138- Use **Context7 MCP** to pull authoritative, up-to-date library docs/snippets:
139 - First resolve the library: `mcp__context7__resolve-library-id` with `libraryName: "refui"`.
140 - Then query: `mcp__context7__query-docs` for the specific API/task (e.g. “`For` track vs indexed”, “`createDOMRenderer` macros”, “`nextTick` vs `tick` semantics”, “classic vs automatic JSX setup”).
141- Use **DeepWiki MCP** for repository-level questions (when the upstream repo is available):
142 - `mcp__deepwiki__read_wiki_structure` then `mcp__deepwiki__ask_question` on `SudoMaker/rEFui` for conceptual/system questions or “where is X documented?”.
143
144If MCP docs still leave ambiguity, ask the user for: the `refui` version, their bundler config (Vite/esbuild/Bun/TS/Babel), and a minimal repro snippet.
145
146## “Which rEFui feature should I use?” (fast mapping)
147
148Use these references when choosing a built-in solution:
149- Idiot-proof do/don’t checklist: `references/dos-and-donts.md`
150- Async UI: `references/async-suspense-transition.md`
151- Overlays/teleports/rich HTML/custom elements: `references/portals-parse-custom-elements.md`
152- Lists/identity/caching/perf: `references/lists-cache-memo.md`
153- Project setup: `references/project-setup.md`
154
155## Non-Negotiables (retained mode)
156
157- Do not write React/Vue/Solid/Svelte primitives (`useState`, hooks, VDOM assumptions, `$:` blocks, etc.). Map them to rEFui signals/effects.
158- Treat component bodies as **setup** (constructor-ish). JSX is evaluated once; signals drive incremental updates afterward.
159- Keep reactive reads reactive:
160 - ✅ Use a signal directly: `<div>{count}</div>`
161 - ✅ Wrap derived expressions: `<div>{$(() => `Count: ${count.value}`)}</div>` or `<div>{computed(() => ...)}</div>`
162 - ❌ Avoid inline `.value` in JSX: `<div>{count.value}</div>` (evaluates once, won’t update)
163- Remember scheduling: signal effects/computed flush at the end of the tick; use `await nextTick()` when you must observe derived updates.
164
165## Hard Rules (idiot-proof guardrails)
166
167- This is **not React**. Component bodies run once; JSX does not re-run. Do not expect re-renders.
168- Do not invent props. If the API is unclear, open the .d.ts or use MCP. Example: `For` has **no** `fallback` prop.
169- `If` / `For` / templates accept a **single** renderable. If you need multiple nodes, wrap them in a container or fragment.
170- `For` empty state: wrap it in `If` and provide a false branch. Example:
171 - `<If condition={$(() => items.value.length)}><For entries={items} track="id">{({ item }) => <Row item={item} />}</For><Empty /></If>`
172- Use `.value` inside `computed` / `$(() => ...)` / `watch` / event handlers, not directly in JSX text/attrs.
173
174## Default Patterns (copy these mentally)
175
176- State: `const x = signal(initial)`
177- Derived: `const y = $(() => /* uses x.value */)` (or `computed(() => ...)`)
178- Effects: `watch(fn)` for reactive computations; `useEffect(setup)` for setup+cleanup; `onDispose(cleanup)` for teardown.
179- Lists:
180 - Keyed: `<For entries={items} track="id">{({ item }) => ...}</For>`
181 - Unkeyed (perf experiments / reorder heavy): `UnKeyed` from `refui/extras/unkeyed.js`
182 - If mutating arrays/objects in place: call `sig.trigger()` after mutation.
183- Async:
184 - `<Async future={promise} fallback={...} catch={...}>{({ result }) => ...}</Async>`
185 - `<Suspense>` for grouping async subtrees
186 - `async` components are supported; pair with fallbacks when needed.
187- DOM directives/events (DOM renderer):
188 - Events: `on:click={...}`, plus options `on-once:*`, `on-passive:*`, `on-capture:*`
189 - Attributes vs props: prefer `attr:` for SVG or when a DOM prop is read-only; use `prop:` to force a property set.
190 - Preset directives (browser preset): `class:x={boolSignal}`, `style:color={valueOrSignal}`
191 - Macros: `m:name={value}` where `name` is registered on the renderer.
192- Refs/handles:
193 - `$ref={sig}` to receive a node/instance in `sig.value`
194 - `$ref={(node) => ...}` callback form
195 - Prefer `expose` prop for imperative child handles (v0.8.0+).
196
197## Workflows
198
199### Fix “UI not updating”
200
2011. Search for JSX `{something.value}` and decide if it must be reactive:
202 - Replace with `{something}` when `something` is already a signal.
203 - Wrap derived text/attrs with `$(() => ...)` / `computed(() => ...)` / `t\`...\``.
2042. If you mutated an object/array held by a signal in-place, add `sig.trigger()` (or replace with a new object/array).
2053. If you read derived values immediately after writes, insert `await nextTick()` before reading computed/DOM-dependent values.
2064. If an effect runs “forever”, ensure it’s created inside a component scope and cleaned up via `useEffect`/`onDispose`.
207
208### Add a feature safely
209
2101. Keep renderer creation at the entry point; do not create renderers inside components.
2112. Localize state: prefer per-component signals over global blobs; use `extract`/`derivedExtract` to reduce fan-out.
2123. For repeated DOM behaviors, register a macro and use it via `m:*` rather than duplicating manual DOM code.
2134. For lists, choose keyed `<For>` unless you have a measured reason to use unkeyed.
214
215### Set up a new project (when asked)
216
2171. Ask only: preferred package manager (`npm`/`pnpm`/`yarn`/`bun`) and language (JS/TS). Do not ask runtime.
2182. Default to JSX automatic runtime + JavaScript + `refui` latest from npm unless the user specifies otherwise.
2193. Follow `references/project-setup.md`.
220
221## Repo Navigation (load only if needed)
222
223Read these files when you need deeper details:
224
225- `references/project-triage.md` for determining JSX mode/renderer/version from a project that uses rEFui as a dependency.
226- `references/project-setup.md` for scaffolding a new project (default: JSX automatic runtime + pure JS).
227- `references/jsx-and-renderers.md` for choosing classic vs automatic and DOM/HTML/Reflow specifics.
228- `references/reactivity-pitfalls.md` for high-signal debugging checklists and anti-patterns.
229- `references/dos-and-donts.md` for per-API/component do’s and don’ts that prevent React-mindset mistakes.
230- `references/async-suspense-transition.md` for `<Async>`, `<Suspense>`, `lazy`, and `Transition`.
231- `references/portals-parse-custom-elements.md` for portals/teleports, HTML parsing, and custom elements.
232- `references/lists-cache-memo.md` for `<For>`, identity, `UnKeyed`, caching, and memoization.
233
234## Resources
235
236### `scripts/`
237- `scripts/refui-audit.mjs`: quick scan for JSX mode + common `.value`-in-JSX pitfalls.
238
239### `references/`
240- `references/project-triage.md`
241- `references/jsx-and-renderers.md`
242- `references/reactivity-pitfalls.md`
243- `references/dos-and-donts.md`
244- `references/async-suspense-transition.md`
245- `references/portals-parse-custom-elements.md`
246- `references/lists-cache-memo.md`
247- `references/project-setup.md`