Custom Renderers

Build rEFui renderers for novel platforms: map your platform’s primitives to the small nodeOps interface and let signals drive updates. Prefer reusing a DOM shim (undom-ng) when possible; write a renderer only when you need platform-specific behavior. If your platform already exposes a DOM-like API, you can often reuse createDOMRenderer with a custom doc.

When to write one

If you already have a DOM-like API

Minimal interface (nodeOps)

Implement these methods and pass them to createRenderer(nodeOps):

setProps is called with the full props object; make it idempotent. Normalize events, styles, and platform-specific attributes here. Clean up in removeNode when needed. When a prop value is a signal, subscribe and update the native prop/handler on change (see DOM/HTML renderers).

Rendering flow

  1. const R = createRenderer(nodeOps)
  2. R.render(root, App) or use JSX (classic: jsxFactory: 'R.c', jsxFragment: 'R.f'; automatic: jsxImportSource: 'refui').
  3. Signals drive retained updates; only touched nodes call setProps/append/remove.

Fragments

If the platform lacks fragments, create an anchor node or manage an array of children; implement isFragment and createFragment accordingly. appendNode/insertBefore should handle fragments by flattening or delegating.

Props and events

Suggested structure

import { createRenderer } from 'refui'

const nodeOps = {
	isNode: (n) => !!n && n.type === 'node',
	createNode: (tag) => platformCreate(tag),
	createTextNode(text) {
		// mirror DOM/HTML behavior: if text is a signal, subscribe
		if (isSignal(text)) {
			const n = platformCreateText('')
			text.connect(() => platformSetText(n, String(peek(text) ?? '')))
			return n
		}
		return platformCreateText(String(text ?? ''))
	},
	createAnchor: () => platformCreateComment(''),
	createFragment: () => platformCreateFragment(),
	removeNode: platformRemove,
	appendNode(parent, ...kids) { kids.forEach(k => platformAppend(parent, k)) },
	insertBefore(node, ref) { platformInsertBefore(node, ref) },
	setProps(node, props) { platformSetProps(node, props) },
	isFragment: (n) => n && n.type === 'fragment'
}

export const R = createRenderer(nodeOps)

Prefer shims when available

Debugging tips

Custom Render Targets

If you have a DOM-like API available on your target platform, you can often pass its doc implementation to createDOMRenderer instead of building a full nodeOps implementation from scratch. This allows you to leverage the existing DOM renderer logic for reactive properties and text.