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
.valuetoo early (non-reactively) or mutated in place withouttrigger().
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> - •
Forhas nofallback; 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. Seereferences/async-suspense-transition.mdfor 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) orjsxImportSource: "refui"(tsconfig/Bun). - •Classic transform: look for
jsxFactory: 'R.c'+jsxFragment: 'R.f'(Vite/esbuild) or/** @jsx R.c */file pragmas.
- •Automatic runtime: look for
- •Identify the host renderer:
- •Browser apps:
createDOMRenderer(defaults)fromrefui/dom+refui/browser(orrefui/presets/browserin older repos). - •SSR/SSG:
createHTMLRenderer()fromrefui/html, thenserialize(). - •Reflow logic-only modules:
refui/reflow(often injected viajsxInject: import { R } from 'refui/reflow'in classic mode).
- •Browser apps:
- •Confirm rEFui version (it changes API details across repos). Prefer the repo’s local docs or installed
refuiexports.
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-idwithlibraryName: "refui". - •Then query:
mcp__context7__query-docsfor the specific API/task (e.g. “Fortrack vs indexed”, “createDOMRenderermacros”, “nextTickvsticksemantics”, “classic vs automatic JSX setup”).
- •First resolve the library:
- •Use DeepWiki MCP for repository-level questions (when the upstream repo is available):
- •
mcp__deepwiki__read_wiki_structurethenmcp__deepwiki__ask_questiononSudoMaker/rEFuifor 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
.valuein JSX:<div>{count.value}</div>(evaluates once, won’t update)
- •✅ Use a signal directly:
- •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:
Forhas nofallbackprop. - •
If/For/ templates accept a single renderable. If you need multiple nodes, wrap them in a container or fragment. - •
Forempty state: wrap it inIfand provide a false branch. Example:- •
<If condition={$(() => items.value.length)}><For entries={items} track="id">{({ item }) => <Row item={item} />}</For><Empty /></If>
- •
- •Use
.valueinsidecomputed/$(() => ...)/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 */)(orcomputed(() => ...)) - •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):
UnKeyedfromrefui/extras/unkeyed.js - •If mutating arrays/objects in place: call
sig.trigger()after mutation.
- •Keyed:
- •Async:
- •
<Async future={promise} fallback={...} catch={...}>{({ result }) => ...}</Async> - •
<Suspense>for grouping async subtrees - •
asynccomponents are supported; pair with fallbacks when needed.
- •
- •DOM directives/events (DOM renderer):
- •Events:
on:click={...}, plus optionson-once:*,on-passive:*,on-capture:* - •Attributes vs props: prefer
attr:for SVG or when a DOM prop is read-only; useprop:to force a property set. - •Preset directives (browser preset):
class:x={boolSignal},style:color={valueOrSignal} - •Macros:
m:name={value}wherenameis registered on the renderer.
- •Events:
- •Refs/handles:
- •
$ref={sig}to receive a node/instance insig.value - •
$ref={(node) => ...}callback form - •Prefer
exposeprop 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}whensomethingis already a signal. - •Wrap derived text/attrs with
$(() => ...)/computed(() => ...)/t\...``.
- •Replace with
- •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/derivedExtractto 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 +
refuilatest 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.mdfor determining JSX mode/renderer/version from a project that uses rEFui as a dependency. - •
references/project-setup.mdfor scaffolding a new project (default: JSX automatic runtime + pure JS). - •
references/jsx-and-renderers.mdfor choosing classic vs automatic and DOM/HTML/Reflow specifics. - •
references/reactivity-pitfalls.mdfor high-signal debugging checklists and anti-patterns. - •
references/dos-and-donts.mdfor per-API/component do’s and don’ts that prevent React-mindset mistakes. - •
references/async-suspense-transition.mdfor<Async>,<Suspense>,lazy, andTransition. - •
references/portals-parse-custom-elements.mdfor portals/teleports, HTML parsing, and custom elements. - •
references/lists-cache-memo.mdfor<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