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.
jsx
1import { signal, $ } from 'refui'
2
3const Counter = () => {
4 const count = signal(0)
5 return (
6 <button on:click={() => count.set(count.value + 1)}>
7 {$(() => `Count: ${count.value}`)}
8 </button>
9 )
10}
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>.
jsx
1import { signal, $, If, For } from 'refui'
2
3const App = () => {
4 const items = signal([{ id: 1, name: 'A' }])
5 return (
6 <If condition={$(() => items.value.length)}>
7 {() => <For entries={items} track="id">{({ item }) => <div>{item.name}</div>}</For>}
8 {() => <div>Empty</div>}
9 </If>
10 )
11}
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.
jsx
1import { signal, $, createContext, useContext } from 'refui'
2
3const Theme = createContext(signal('light'), 'Theme')
4
5const Button = () => {
6 const theme = useContext(Theme)
7 return <button class:dark={$(() => theme.value === 'dark')}>OK</button>
8}
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).
jsx
1import { createDOMRenderer } from 'refui/dom'
2import { defaults } from 'refui/browser'
3
4createDOMRenderer(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