Skip to content

Package

wybthon (package)

wybthon

Wybthon: SolidJS for Python, running in the browser on Pyodide.

Wybthon brings SolidJS 2.0's signals-first reactive model to Python. Component bodies run once at mount; reactivity flows through reactive holes: accessors embedded in the VNode tree that update only the DOM nodes that depend on them. A virtual DOM batches every mutation into a single crossing of the Python-to-JavaScript bridge.

Highlights of the reactive model:

  • Automatic batching. Signal writes are staged and applied on the next flush (a microtask, and after each event handler). There is no batch(); everything batches. Call flush to settle now.
  • Typed accessors. create_signal returns (Accessor[T], Setter[T]); call the accessor to read (tracked), .peek() to read without subscribing.
  • Typed props. Components declare inputs on a Props class; pyright and mypy check every call without a plugin.
  • Async-first, with transitions. A memo whose body is async def (or an async generator) is an async computation: Loading boundaries show fallbacks until it first resolves. A later recompute opens a transition: the UI that depends on the changed input holds its previous, consistent state until the new value lands, so a new id never shows next to old data. is_pending / latest observe the in-flight state; refresh and resolve drive it imperatively.
  • Actions are transactions. action holds a transition open while a mutation runs, so its writes land together; create_optimistic and create_optimistic_store show temporary values immediately and revert when it settles; affects and until describe what the action changes and wait for it to land.
  • Draft-first stores. create_store setters take a function that mutates a draft with plain Python.
  • Server rendering. wybthon.server renders the same components to HTML in CPython, including async data and out-of-order streaming; hydrate adopts that HTML in the browser instead of rebuilding it.
  • Dev diagnostics. Writes inside a tracking scope raise WriteInScopeError; reading a signal at the top level of a component body warns.

Everything is importable outside a browser (CPython), so unit tests and tooling run anywhere; the DOM is only touched when rendering.

Example

A minimal counter component:

from wybthon import Prop, Props, button, component, create_signal, div, p, prop, render


class CounterProps(Props):
    initial: Prop[int] = prop(default=0)


@component
def Counter(props: CounterProps):
    count, set_count = create_signal(props.initial.peek())
    return div(
        p(t"Count: {count}"),
        button("+1", on_click=lambda: set_count(lambda n: n + 1)),
    )


render(Counter(initial=5), "#app")
See Also

Modules:

Name Description
assets

Explicit lazy application chunks produced by wyb build.

build

Deterministic static app bundles, prerendered pages, and a production preview server.

component

The @component decorator and the Component type it produces.

context

Context: pass values down the tree without threading props.

dev

The wyb command: develop, build, and preview Wybthon projects.

diagnostics

Opt-in runtime counters and JSON-ready ownership graph inspection.

dom

Lightweight DOM wrapper utilities for Pyodide/browser environments.

error_boundary

Errored: catch render and effect errors in a subtree.

events

Delegated event handling for the batched renderer.

flow

Control flow: Show, For, Repeat, Switch/Match, dynamic, and client_only.

forms

Form state, validation helpers, and accessibility attribute utilities.

html

Pythonic HTML element helpers that wrap h().

kernel

Batched DOM command buffer and rendering backends.

lazy

Lazy-loaded components integrated with Loading boundaries.

loading

Loading and Reveal: fallback UI for async computations.

portal

Portal component for rendering children into a different DOM container.

reactivity

Wybthon's reactive system.

reconciler

Reconciliation engine: mounting, patching, and unmounting VNode trees.

request

The request being server-rendered, and the response head the render declares.

router

Client-side router components and navigation helpers for Pyodide apps.

scheduling

Cooperative CPU work that gives the browser opportunities to paint.

server

Render components to HTML on the server, for hydration in the browser.

store

Transactional, entity-based reactive mappings and sequences.

svg

SVG element helpers.

testing

Render components in plain CPython and query them like a user would.

virtual

Fixed-height list virtualization built on owned virtual DOM list regions.

vnode

Virtual node data structure and tree-building helpers.

Classes:

Name Description
Component

A run-once component produced by component.

Context

A context token that also acts as its provider component.

ContextNotFoundError

Raised by use_context when no provider is found and no default exists.

Element

Thin wrapper around a DOM node with convenience methods.

Ref

Mutable container holding a reference to a mounted Element.

DomEvent

The event object Wybthon passes to delegated handlers.

EventHandler

A handler with native listener options, constructed with event.

Match

A branch of a Switch.

Accessor

A zero-argument callable that returns a reactive value.

Action

A wrapped mutation returned by action.

ChildrenAccessor

The resolved children returned by children.

Memo

Read-only derived value; what create_memo returns.

NotReadyError

Raised when reading an async computation that has no value yet.

Owner

A reactive ownership scope.

ParentProps

Props for a component that renders children.

Prop

A reactive component input, declared on a Props class.

Props

Base class for a component's declared inputs.

ServerError

An async memo failed on the server; raised again on the client while hydrating.

Setter

The write half of create_signal.

Signal

Mutable reactive container; the accessor half of create_signal.

WriteInScopeError

Raised (in dev mode) when a signal is written inside a tracking scope.

Root

A mounted application root returned by render.

RequestEvent

One request being rendered on the server.

ResponseHead

The status and headers a server render declares.

VNode

Virtual node representing an element, text, component, fragment, or hole.

Draft

A mapping draft that expires when its callback returns.

DraftExpiredError

A draft was used after its setter or deriving function finished.

DraftList

A sequence draft with standard Python mutation methods.

Store

Read-only reactive mapping with optional attribute access to data keys.

StoreList

Read-only reactive sequence. Entity proxies survive reorders.

StoreSetter

Apply an atomic draft edit. Exceptions discard all changes from the call.

Functions:

Name Description
is_dev_mode

Return whether development mode is currently active.

set_dev_mode

Enable or disable development mode diagnostics globally.

create_context

Create a Context.

use_context

Read the nearest provided value for ctx.

Errored

Catch errors raised while rendering children and show a fallback.

event

Configure a native listener. Ordinary handlers use batched delegation.

For

Render a list with a stable subtree per row.

Hydration

Mark a subtree for hydration; a passthrough in Wybthon.

NoHydration

Render children on the server as static HTML the browser won't hydrate.

Repeat

Render children(i) for i in range(start, start + count) with no diffing.

Show

Render children while when is truthy, else fallback.

Switch

Render the first Match whose condition is truthy.

client_only

Render children only in the browser, after hydration.

dynamic

Turn an accessor for a component (or tag) into a component you can call.

is_hydrating

Whether the browser is adopting server-rendered DOM right now.

element

Create a helper fn(*children, **props) -> VNode for any tag name.

Loading

Show a fallback while async reads under the boundary aren't ready.

Reveal

Coordinate the reveal order of multiple Loading boundaries.

Portal

Render children into a different DOM container.

action

Wrap a mutation in a transaction.

affects

Declare, inside an action, which values the action is going to change.

children

Resolve a component's children once, so it can inspect or reuse them.

create_effect

Create a side effect that re-runs when its tracked sources change.

create_memo

Create a derived value that recomputes when its sources change.

create_optimistic

Create a value whose writes are optimistic.

create_owner

Create an ownership scope owned by the current one.

create_reaction

Separate tracking from re-execution: run effect once when tracked reads change.

create_render_effect

Create a split effect that runs in the render phase, before the DOM commit.

create_root

Run fn inside a root owned by the current scope unless explicitly detached.

create_signal

Create a reactive signal and return its (getter, setter) pair.

create_tracked_effect

Run a tracked callback after DOM commit, disposing its scope before each run.

create_unique_id

Return a unique id string (for for/id attribute pairs).

flush

Apply staged writes, run every dirty effect, and commit the DOM now.

get_observer

Return the computation currently tracking reads, or None.

get_owner

Return the active ownership scope, or None outside any scope.

is_accessor

Return True when value is a reactive expression: an Accessor or a zero-arg function.

is_disposed

Return whether owner has been disposed.

is_pending

Return True while a change is in flight for the value fn reads.

is_server

Return True while a server render is in progress.

latest

Evaluate fn against the newest state, without ever suspending.

map_array

Map a reactive list to mapped rows with per-row owner scopes.

merge

Merge prop sources into one reactive mapping; later sources win.

omit

Return a reactive view of source without some keys.

on_cleanup

Register fn to run when the active scope is disposed or re-runs.

on_settled

Run fn once the current render has settled and committed to the DOM.

prop

Declare a Prop[T] field's default.

refresh

Recompute a derived read quietly and return an awaitable for its settled value.

repeat

Map an integer range to rows, each with its own owner scope.

resolve

Return an awaitable for the next settled value of fn().

run_with_owner

Run fn under owner (pass None for no ownership) and return its result.

until

Return an awaitable for the settled, truthy value of pred().

untrack

Run fn without tracking any reactive reads.

hydrate

Adopt server-rendered HTML under container and make it interactive.

render

Render a tree into a container element.

get_request_event

Return the request being server-rendered, or None in the browser.

http_header

Declare a response header while the calling scope is alive (server only).

http_status

Declare the response status while the calling scope is alive (server only).

Fragment

Group children without adding a wrapper element.

h

Create a VNode from a tag, props, and children.

hole

Create a reactive hole explicitly, optionally with a key.

create_optimistic_store

Layer optimistic draft edits over a reactive authoritative source.

create_projection

Create a read-only derived store with memo readiness and refresh semantics.

create_store

Create a reactive mapping or sequence and its atomic draft setter.

deep

Return a detached snapshot, tracking changes only within this subtree.

reconcile

Prepare replacement data, preserving matched list entity identities.

snapshot

Return a detached, untracked copy of the currently visible state.

Component

Component(fn: Callable[..., Any])

A run-once component produced by component.

Calling it returns a VNode; the reconciler runs the wrapped function once per mount.

Context

Context(default: Any = _MISSING, name: str | None = None)

A context token that also acts as its provider component.

Created by create_context. Calling the context with a value and children returns a provider VNode:

Theme(value, *children)
Theme("dark", App())
Theme(lambda: theme(), App())     # reactive value

Attributes:

Name Type Description
default

Value returned by use_context when no provider is found, if one was declared.

name

Optional label for diagnostics.

has_default property

has_default: bool

Whether the context was created with a default value.

ContextNotFoundError

Bases: LookupError

Raised by use_context when no provider is found and no default exists.

Element

Element(tag: str | None = None, existing: bool = False, node: Any = None, node_id: int | None = None)

Thin wrapper around a DOM node with convenience methods.

Element can be constructed in four ways:

  • With a tag name to create a brand-new node (Element("div")).
  • With a CSS selector and existing=True to wrap an existing node (Element("#root", existing=True)).
  • With an opaque node value to wrap a node returned by another API.
  • With a kernel node_id (used internally by the renderer for refs and event targets); the raw node is materialized lazily on first access.

The wrapper proxies common form-input properties (value, checked, files) so handlers can read state from e.target.value exactly as in React or SolidJS.

Create a new element, wrap an existing one, or wrap a node handle.

Parameters:

Name Type Description Default
tag str | None

Tag name ("div") or, when existing=True, a CSS selector identifying the node to wrap.

None
existing bool

If True, query the document for tag instead of creating a new element.

False
node Any

Raw underlying DOM node to wrap. When provided, tag and existing are ignored.

None
node_id int | None

Kernel node id to wrap. The raw node is fetched on first element access.

None

Raises:

Type Description
ValueError

If neither node, node_id, nor a usable tag is provided.

Methods:

Name Description
set_text

Replace the text content of this element.

append_to

Append this element to parent.

append

Append an Element or a text string as a child node.

remove

Detach this element from its parent (no-op if already detached).

load_html

Fetch HTML from url and assign it to innerHTML.

set_html

Replace this element's content with the provided HTML string.

set_attr

Set an attribute on this element, with text-node fallbacks.

get_attr

Return the attribute value for name, or None when absent.

remove_attr

Remove an attribute from this element.

set_style

Set CSS properties using a dict and/or keyword arguments.

add_class

Add one or more CSS classes to this element.

remove_class

Remove one or more CSS classes from this element.

toggle_class

Toggle a CSS class, optionally forcing on/off.

has_class

Return True if the element currently has the given class.

query

Query a single element by CSS selector.

query_all

Query all matching elements by CSS selector.

find

Return the first matching descendant Element, or None.

find_all

Return all matching descendant elements as a list.

attach_ref

Store this element on ref.current.

Attributes:

Name Type Description
element Any

The raw underlying DOM node, materialized on first access.

node_id int

This element's kernel node id, registering the raw node if needed.

value Any

Current value of an <input>, <textarea>, or <select>.

checked bool

Checked state of a checkbox or radio input.

files Any

FileList for file inputs (<input type="file">), or None.

element property

element: Any

The raw underlying DOM node, materialized on first access.

For id-backed elements this commits pending batched DOM ops first, so the node exists and reflects every queued mutation.

node_id property

node_id: int

This element's kernel node id, registering the raw node if needed.

value property writable

value: Any

Current value of an <input>, <textarea>, or <select>.

checked property writable

checked: bool

Checked state of a checkbox or radio input.

files property

files: Any

FileList for file inputs (<input type="file">), or None.

set_text

set_text(text: str) -> None

Replace the text content of this element.

append_to

append_to(parent: Element) -> None

Append this element to parent.

append

append(child: Element | str) -> None

Append an Element or a text string as a child node.

remove

remove() -> None

Detach this element from its parent (no-op if already detached).

load_html async

load_html(url: str) -> None

Fetch HTML from url and assign it to innerHTML.

Parameters:

Name Type Description Default
url str

URL to fetch via the browser's fetch API.

required

set_html

set_html(html: str) -> None

Replace this element's content with the provided HTML string.

Caution

This bypasses the renderer's diffing and does not sanitize input. Avoid passing untrusted HTML.

set_attr

set_attr(name: str, value: str | int | float | bool) -> None

Set an attribute on this element, with text-node fallbacks.

Parameters:

Name Type Description Default
name str

Attribute name (or nodeValue/text for text nodes).

required
value str | int | float | bool

Attribute value; coerced to str.

required

get_attr

get_attr(name: str) -> str | None

Return the attribute value for name, or None when absent.

remove_attr

remove_attr(name: str) -> None

Remove an attribute from this element.

set_style

set_style(styles: dict[str, str | int] | None = None, **style_kwargs: str | int) -> None

Set CSS properties using a dict and/or keyword arguments.

Parameters:

Name Type Description Default
styles dict[str, str | int] | None

Optional mapping of CSS property name to value.

None
**style_kwargs str | int

Additional CSS properties (last write wins if a key appears in both).

{}

add_class

add_class(*class_names: str) -> None

Add one or more CSS classes to this element.

remove_class

remove_class(*class_names: str) -> None

Remove one or more CSS classes from this element.

toggle_class

toggle_class(class_name: str, force: bool | None = None) -> None

Toggle a CSS class, optionally forcing on/off.

Parameters:

Name Type Description Default
class_name str

Class to toggle.

required
force bool | None

Pass True to ensure the class is added, False to ensure it's removed; None toggles based on the current state.

None

has_class

has_class(class_name: str) -> bool

Return True if the element currently has the given class.

query classmethod

query(selector: str, within: Element | None = None) -> Element | None

Query a single element by CSS selector.

Commits pending batched DOM ops first so nodes created earlier in the same update are visible to the query.

Parameters:

Name Type Description Default
selector str

CSS selector string.

required
within Element | None

Optional parent Element to scope the query to. Defaults to document.

None

Returns:

Type Description
Element | None

The first matching Element, or None if no node matches.

query_all classmethod

query_all(selector: str, within: Element | None = None) -> list[Element]

Query all matching elements by CSS selector.

Parameters:

Name Type Description Default
selector str

CSS selector string.

required
within Element | None

Optional parent Element to scope the query to. Defaults to document.

None

Returns:

Type Description
list[Element]

A list of wrapped Element instances (possibly empty).

find

find(selector: str) -> Element | None

Return the first matching descendant Element, or None.

find_all

find_all(selector: str) -> list[Element]

Return all matching descendant elements as a list.

attach_ref

attach_ref(ref: Ref) -> None

Store this element on ref.current.

Ref

Ref()

Mutable container holding a reference to a mounted Element.

Pass a Ref to an element's ref= prop. After mount ref.current is the wrapped element; after unmount it's reset to None. The ref prop also accepts a callback ref(el) or a list mixing both, so a component can forward a parent's ref and keep its own:

class FancyInputProps(Props):
    ref: Ref | Callable[[Element], Any] | None = None


@component
def FancyInput(props: FancyInputProps):
    local = Ref()
    on_settled(lambda: local.current.element.focus())
    return input_(type="text", ref=[local, props.ref])

Refs are assigned during mount, so read them in on_settled or an effect, not at the top of the component body.

DomEvent

DomEvent(payload: dict[str, Any], current_target: Any | None = None)

The event object Wybthon passes to delegated handlers.

Built from the kernel's dispatch payload rather than a JsProxy, so reading it is free of bridge crossings. Use raw to reach the native event object for anything not covered by the payload.

Attributes:

Name Type Description
type

Event type string (e.g., "click").

target

Payload-backed view of the original event target, exposing value, checked, files, and element.

current_target

The element whose handler is currently running (an id-backed Element).

key

KeyboardEvent.key, or None for non-keyboard events.

code

KeyboardEvent.code, or None.

alt_key

Whether Alt was held.

ctrl_key

Whether Ctrl was held.

meta_key

Whether Meta/Cmd was held.

shift_key

Whether Shift was held.

button

MouseEvent.button (0 for primary).

client_x

Pointer x position, when applicable.

client_y

Pointer y position, when applicable.

Build an event from a dispatch payload dict.

Parameters:

Name Type Description Default
payload dict[str, Any]

Parsed dispatch payload from the kernel.

required
current_target Any | None

The Element whose handler is being invoked.

None

Methods:

Name Description
prevent_default

Prevent the default browser action for this event.

stop_propagation

Stop the delegated dispatch from walking further up the tree.

raw property

raw: Any

The native browser event object (escape hatch).

Only valid synchronously during dispatch; returns None afterwards.

prevent_default

prevent_default() -> None

Prevent the default browser action for this event.

stop_propagation

stop_propagation() -> None

Stop the delegated dispatch from walking further up the tree.

Also stops native propagation so other JS listeners above don't fire.

EventHandler dataclass

EventHandler(callback: Callable[..., Any], capture: bool = False, passive: bool = False, once: bool = False)

A handler with native listener options, constructed with event.

Match

Match(when: Any, children: Any = None, *, keyed: bool = False)

A branch of a Switch.

Parameters:

Name Type Description Default
when Any

Condition accessor or plain value.

required
children Any

VNode, zero-arg callable, or (value) -> VNode receiving an Accessor (or the raw value with keyed=True).

None
keyed bool

Re-create the branch on every value change.

False

Accessor

A zero-argument callable that returns a reactive value.

Calling an accessor inside a tracking scope (a memo, an effect's compute stage, or a reactive hole) subscribes that scope to the value. peek reads without subscribing.

Every reactive read in Wybthon goes through an Accessor: create_signal getters are Signals, create_memo returns a Memo, and component parameters are Props. Embedding an accessor in a VNode tree creates a reactive hole. Type it as Accessor[T] in signatures when you accept any of them.

Methods:

Name Description
peek

Return the current value without subscribing anything.

peek

peek() -> T

Return the current value without subscribing anything.

Action

Action(fn: Callable[P, Any])

A wrapped mutation returned by action.

Calling it invokes the wrapped function inside a transaction. The pending accessor is True while any invocation is in flight; it reveals immediately, so it's the right thing to bind a disabled button or a spinner to.

Attributes:

Name Type Description
pending Accessor[bool]

Tracked accessor: True while an invocation is in flight.

pending instance-attribute

pending: Accessor[bool] = _core.run_with_owner(None, lambda: Memo(lambda: count() > 0))

Tracked accessor: True while an invocation is in flight.

ChildrenAccessor

ChildrenAccessor(memo: Memo[list[Any]])

Bases: Accessor[Any]

The resolved children returned by children.

Calling it returns the single resolved child, or a list when there are several (or none); to_array always returns a list.

Methods:

Name Description
to_array

Return the resolved children as a list (tracked).

to_array

to_array() -> list[Any]

Return the resolved children as a list (tracked).

Memo

Memo(fn: Callable[..., T], *, equals: Any = _DEFAULT_EQUALS, lazy: bool = False, unobserved: Callable[[], Any] | None = None, name: str | None = None, loading_value: Any = _MISSING, ssr_source: str = 'server')

Bases: Computation, Accessor[T]

Read-only derived value; what create_memo returns.

A Memo is both a Computation (it tracks sources and recomputes lazily) and an Accessor (call it to read). Recomputation happens on read after a source changed, and observers are notified only when the recomputed value differs under the memo's equals policy.

Async bodies (async def, or an async generator) make the memo an async computation; see create_memo.

Methods:

Name Description
peek

Return the (up-to-date) value without subscribing; None while not ready.

peek

peek() -> T

Return the (up-to-date) value without subscribing; None while not ready.

NotReadyError

Bases: Exception

Raised when reading an async computation that has no value yet.

Propagates "not ready" through the graph: a memo whose body reads a pending async memo becomes pending itself, and a reactive hole that hits NotReadyError keeps its previous content while the nearest Loading boundary shows its fallback.

Application code rarely raises or catches this directly; use is_pending and latest to observe pending state without suspending.

Owner

Owner()

A reactive ownership scope.

Owners form a tree. Disposing an owner disposes its children first (depth-first), then runs its own cleanup callbacks LIFO. Every computation is an owner, as is each component instance, each For row, and each create_root.

Owners also carry context values (see create_context) and an optional error handler installed by Errored.

Methods:

Name Description
dispose

Tear down this owner and every descendant.

dispose

dispose() -> None

Tear down this owner and every descendant.

Children are disposed depth-first, then this owner's cleanups run in LIFO order, then it detaches from its parent. Later calls are no-ops.

ParentProps

ParentProps(**values: Any)

Bases: Props

Props for a component that renders children.

Prop

Prop(field: _Field)

A reactive component input, declared on a Props class.

The parent may pass a T, an accessor of T, or a zero-argument function returning T. Reading the field on the component's props returns an Accessor: place it in the tree to create a reactive hole, call it inside a tracking scope, or use .peek() for an intentional one-time read.

Props

Props(**values: Any)

Bases: _PropsBase

Base class for a component's declared inputs.

Subclass it, annotate fields, and take one parameter of that type in the component. Every component also accepts key, which gives the instance a stable identity for keyed reconciliation.

At run time the reconciler builds one instance per mounted component. Reading a Prop[T] field returns an accessor that tracks the parent's current value, so a parent that later passes a new value (or an accessor) updates the child without re-running it.

ServerError

ServerError(message: str, type_name: str = 'Exception')

Bases: RuntimeError

An async memo failed on the server; raised again on the client while hydrating.

The original exception doesn't survive serialization, so the client sees its type name and message. An Errored boundary shows the same fallback on both sides; resetting the boundary runs the computation again in the browser.

Setter

Bases: Protocol

The write half of create_signal.

Call it with a new value, or with an updater (current) -> new for a functional update. Returns the value that was staged.

Signal

Signal(value: T, *, equals: Any = _DEFAULT_EQUALS, unobserved: Callable[[], Any] | None = None, name: str | None = None)

Bases: Accessor[T]

Mutable reactive container; the accessor half of create_signal.

Reading a Signal (calling it) returns the committed value and subscribes the active tracking scope. set stages a write that becomes visible at the next flush.

While a transition holds the signal (its last write is waiting for async work to land), reads from outside the graph, such as event handlers, keep returning the value the UI shows; reads inside memos, effects, and holes see the new value.

Most code destructures create_signal into (getter, setter); Signal is public so it can appear in type hints and so self-contained reactive fields (as in Field) can carry their own setter.

Parameters:

Name Type Description Default
value T

The initial value.

required
equals Any

Equality policy; see create_signal.

_DEFAULT_EQUALS
unobserved Callable[[], Any] | None

Optional callback invoked when the signal loses its last subscriber (resource cleanup).

None
name str | None

Optional label used in dev-mode diagnostics.

None

Methods:

Name Description
peek

Return the committed value without subscribing.

set

Stage a write; the new value is visible after the next flush.

peek

peek() -> T

Return the committed value without subscribing.

set

set(value: T | Callable[[T], T]) -> T

Stage a write; the new value is visible after the next flush.

Supports functional updates: when value is callable it receives the latest value (including any write staged earlier in the same batch) and its result is stored, so two set(lambda n: n + 1) calls in one handler add two. To store a callable as the value, return it from an updater: set(lambda _: my_fn).

In dev mode, writing from inside a tracking scope raises WriteInScopeError.

Parameters:

Name Type Description Default
value T | Callable[[T], T]

The new value, or an updater (current) -> new.

required

Returns:

Type Description
T

The value that was staged.

WriteInScopeError

Bases: RuntimeError

Raised (in dev mode) when a signal is written inside a tracking scope.

Writing a signal from a memo body, a single-function effect, or a reactive hole is almost always a bug: derive the value with create_memo instead, or move the write into the untracked apply stage of a split create_effect, an event handler, or an action.

Root

Root(container: Element, vnode: VNode, owner: Owner)

A mounted application root returned by render.

Attributes:

Name Type Description
container

The container Element.

vnode

The currently rendered root VNode.

Methods:

Name Description
dispose

Unmount the tree, dispose every reactive scope, and stop event delegation.

node_id property

node_id: int

Kernel node id of the container.

dispose

dispose() -> None

Unmount the tree, dispose every reactive scope, and stop event delegation.

RequestEvent dataclass

RequestEvent(url: str = '/', request: Any = None, locals: dict[str, Any] = dict(), response: ResponseHead = ResponseHead())

One request being rendered on the server.

Attributes:

Name Type Description
url str

The request path and query, read by the router.

request Any

The framework's request object, passed through as is.

locals dict[str, Any]

Per-request values for the application (the signed-in user, a database session, and so on).

response ResponseHead

The response head the render declares.

ResponseHead dataclass

ResponseHead(status: int = 200, status_text: str | None = None, headers: dict[str, list[str]] = dict(), committed: bool = False, _statuses: list[tuple[object, int, str | None]] = list(), _headers: list[tuple[object, str, str, bool]] = list(), _base: tuple[int, str | None, dict[str, list[str]]] | None = None)

The status and headers a server render declares.

Attributes:

Name Type Description
status int

The HTTP status code.

status_text str | None

An optional reason phrase.

headers dict[str, list[str]]

Header values by lowercase name, in declaration order.

committed bool

Set once the head can no longer change (the render finished, or a stream sent its shell).

Methods:

Name Description
header_items

Return (name, value) pairs, one per value, for an HTTP response.

header_items

header_items() -> list[tuple[str, str]]

Return (name, value) pairs, one per value, for an HTTP response.

VNode

VNode(tag: str | Callable[..., Any] | None, props: PropsDict | None = None, children: list[Any] | None = None, key: str | int | None = None)

Virtual node representing an element, text, component, fragment, or hole.

Uses __slots__ for a compact layout. The renderer populates the internal attributes (el, subtree, render_effect, component_ctx, ns, _frag_end) when the VNode mounts.

Attributes:

Name Type Description
tag

Element tag name ("div"), a special tag ("_text", "_hole", "_fragment"), or a component.

props PropsDict

Mapping of prop names to values: attributes, event handlers, refs, and reactive bindings.

children list[Any]

Child VNodes (mixed values before normalization).

key

Optional stable identity for keyed reconciliation.

el int | None

Kernel node id once mounted (for fragments the start marker; for holes the end marker).

owner_scope Any

Optional reactive Owner to mount under. Set by list primitives so a cached row's effects belong to the row's scope rather than to the list's re-running effect.

Draft

Draft(node: _Node, session: _Session | None = None)

Bases: Store[S], MutableMapping[str, Any]

A mapping draft that expires when its callback returns.

DraftExpiredError

Bases: RuntimeError

A draft was used after its setter or deriving function finished.

DraftList

DraftList(node: _Node, session: _Session | None = None)

Bases: StoreList[T], MutableSequence[T]

A sequence draft with standard Python mutation methods.

Methods:

Name Description
insert

Insert a value at a draft index.

extend

Append values in one recorded splice.

clear

Remove all items in one transactional edit.

sort

Sort entities without changing their identities.

reverse

Reverse entity order in the draft.

move

Move a contiguous range to a final index, preserving its entities.

insert

insert(index: int, value: T) -> None

Insert a value at a draft index.

extend

extend(values: Iterable[T]) -> None

Append values in one recorded splice.

clear

clear() -> None

Remove all items in one transactional edit.

sort

sort(*, key: Callable[[T], Any] | None = None, reverse: bool = False) -> None

Sort entities without changing their identities.

reverse

reverse() -> None

Reverse entity order in the draft.

move

move(start: int, target: int, count: int = 1) -> None

Move a contiguous range to a final index, preserving its entities.

Store

Store(node: _Node, session: _Session | None = None)

Bases: _Proxy, Mapping[str, Any]

Read-only reactive mapping with optional attribute access to data keys.

Missing subscriptions raise KeyError; missing attributes raise AttributeError. Mapping methods take precedence over data keys: use store["items"].

Methods:

Name Description
get

Return the value for key, or default when absent (tracked either way).

get

get(key: str, default: Any = None) -> Any

Return the value for key, or default when absent (tracked either way).

A tracked read of an absent key subscribes to it, so a computation reading store.get(row_id) re-runs when that key appears.

StoreList

StoreList(node: _Node, session: _Session | None = None)

Bases: _Proxy, Sequence[T]

Read-only reactive sequence. Entity proxies survive reorders.

StoreSetter

StoreSetter(node: _Node)

Apply an atomic draft edit. Exceptions discard all changes from the call.

is_dev_mode

is_dev_mode() -> bool

Return whether development mode is currently active.

set_dev_mode

set_dev_mode(enabled: bool) -> None

Enable or disable development mode diagnostics globally.

Parameters:

Name Type Description Default
enabled bool

When False, warn and warn_once become no-ops, WriteInScopeError is not raised, and tracebacks are suppressed in log_error.

required

create_context

create_context(default: T, *, name: str | None = None) -> Context[T]
create_context(*, name: str | None = None) -> Context[T]
create_context(default: Any = _MISSING, *, name: str | None = None) -> Context[Any]

Create a Context.

Parameters:

Name Type Description Default
default Any

Optional value returned by use_context when no provider is above the reader. Without one, reading outside a provider raises ContextNotFoundError.

_MISSING
name str | None

Optional label for diagnostics.

None

Returns:

Type Description
Context[Any]

A new Context; call it to provide, pass it to use_context to read.

use_context

use_context(ctx: Context[T]) -> T

Read the nearest provided value for ctx.

Walks up the ownership tree from the active scope and returns the value exactly as the provider received it: a signal or accessor stays a signal or accessor (call it where you need the value), a static value is returned directly.

Raises:

Type Description
ContextNotFoundError

No provider is above the caller and the context has no default.

Errored

Errored(children: Any = None, *, fallback: Any = None, on_error: Callable[[BaseException], Any] | None = None, reset_on: Any = None) -> VNode

Catch errors raised while rendering children and show a fallback.

Parameters:

Name Type Description Default
children Any

Content rendered while no error is active: a VNode, a zero-arg callable, or a list of either.

None
fallback Any

A VNode, a string, or a callable (err, reset) -> VNode (or (err) -> VNode). err is an accessor for the caught exception; reset() clears the error and re-renders the children.

None
on_error Callable[[BaseException], Any] | None

Optional callback invoked with the caught exception.

None
reset_on Any

An accessor (or plain value) whose change clears the current error automatically, for example the current route.

None

Returns:

Type Description
VNode

A component VNode that swaps to the fallback

VNode

whenever a descendant raises.

event

event(handler: Callable[..., Any], *, capture: bool = False, passive: bool = False, once: bool = False) -> EventHandler

Configure a native listener. Ordinary handlers use batched delegation.

For

For(each: Any, children: Callable[..., Any], fallback: Any = None, *, keyed: bool | Callable[[Any], Any] = True) -> VNode

Render a list with a stable subtree per row.

The mapping callback runs once per row; when the list changes, existing rows keep their DOM and are only moved, never re-diffed. A row's reactive scope is disposed when it leaves the list.

keyed selects how rows are matched, and with it the callback shape:

  • True (default): match by identity (scalars by value). children(item, index) receives the raw item and an Accessor[int] index.
  • False: match by position. children(item, index) receives an Accessor for the item at that position and an int index.
  • a callable key(item): match by key, updating the row in place when a new object has the same key. children(item, index) receives accessors for both.

Parameters:

Name Type Description Default
each Any

List accessor (or a plain list, which renders once).

required
children Callable[..., Any]

The row callback.

required
fallback Any

Rendered when the list is empty.

None
keyed bool | Callable[[Any], Any]

Matching strategy.

True
Example
# With a key function both arguments are accessors.
For(todos, lambda todo, i: li(lambda: todo()["title"]), keyed=lambda t: t["id"])

Hydration

Hydration(*children: Any, id: str | None = None) -> VNode

Mark a subtree for hydration; a passthrough in Wybthon.

Solid 2.0 uses <Hydration> to re-enable hydration for an island inside <NoHydration>. Wybthon keeps a NoHydration region static as a whole while hydrating, so this renders its children unchanged. It's provided so code shared with Solid's model reads the same.

Parameters:

Name Type Description Default
*children Any

The content.

()
id str | None

Accepted for parity; unused.

None

NoHydration

NoHydration(*children: Any) -> VNode

Render children on the server as static HTML the browser won't hydrate.

While the browser hydrates, the region's server-rendered DOM is kept as is and nothing inside it is mounted, so it costs no Python work and never updates. In a page that wasn't server-rendered, the children render normally. Use it for content that never changes after the first paint: article bodies, footers, legal text.

Matches Solid 2.0's <NoHydration>.

Repeat

Repeat(count: Any, children: Callable[[int], Any], fallback: Any = None, *, start: int | Any = 0) -> VNode

Render children(i) for i in range(start, start + count) with no diffing.

Rendering is driven purely by the count: growing mounts new tail slots, shrinking disposes them, and nothing else is touched. Use it for pagination dots, ratings, skeletons, and other count-driven UI.

Parameters:

Name Type Description Default
count Any

Count accessor or plain integer.

required
children Callable[[int], Any]

(index: int) -> VNode, rendered once per slot.

required
fallback Any

Rendered when the count is zero.

None
start int | Any

First index (accessor or int).

0
Example
Repeat(rating, lambda i: span("*"))

Show

Show(when: Any, children: Any = None, fallback: Any = None, *, keyed: bool = False) -> VNode

Render children while when is truthy, else fallback.

Only the truthiness of when is tracked by default: the branch re-renders when the condition flips, not on every value change. A callable children may accept one argument, an Accessor for the (truthy) value, so inner holes can read it reactively.

With keyed=True, children re-renders whenever the value itself changes and receives the raw value.

Parameters:

Name Type Description Default
when Any

Condition accessor or plain value.

required
children Any

VNode, zero-arg callable, or (value) -> VNode.

None
fallback Any

Rendered when when is falsy.

None
keyed bool

Re-create the branch on every value change.

False
Example
Show(user, lambda u: p("Hello, ", lambda: u().name), fallback=p("Sign in"))

Switch

Switch(*matches: Match, fallback: Any = None) -> VNode

Render the first Match whose condition is truthy.

Conditions are evaluated in order inside the switch's own scope; only a change in which branch matches re-renders, so unrelated value changes are ignored (unless a branch is keyed).

Switch(
    Match(lambda: status() == "loading", lambda: p("Loading...")),
    Match(lambda: status() == "ready", lambda: p("Ready")),
    fallback=lambda: p("Unknown"),
)

client_only

client_only(children: Any, *, fallback: Any = None) -> VNode

Render children only in the browser, after hydration.

During a server render, and while the browser hydrates that render, fallback shows instead; the children replace it as soon as hydration has committed. In a page that wasn't server-rendered, the children render immediately. Use it for widgets that need browser APIs or whose output depends on the browser (a map, a chart measured from the viewport, the user's local time).

Parameters:

Name Type Description Default
children Any

A VNode or a zero-arg callable returning the content.

required
fallback Any

What the server renders, and what shows until hydration finishes.

None
Example
client_only(lambda: Chart(data=data), fallback=p("Loading chart..."))

dynamic

dynamic(source: Any) -> Callable[..., VNode]

Turn an accessor for a component (or tag) into a component you can call.

The returned callable behaves like any component: call it with children and props to get a VNode. Each instance re-mounts when source resolves to a different component; while source is an async computation with no value yet, the instance keeps its current content and the nearest Loading shows its fallback. Passing None renders nothing.

This is the counterpart of SolidJS 2.0's dynamic(). For a one-off use, call the result inline: dynamic(lambda: views[kind()])(title="Hi").

Parameters:

Name Type Description Default
source Any

An accessor returning a component, a tag name, or None; a plain component or tag is accepted too.

required
Example
Editor = dynamic(lambda: RichEditor if rich_mode() else PlainEditor)

def Page():
    return div(Editor(value=draft, on_change=set_draft))

is_hydrating

is_hydrating() -> bool

Whether the browser is adopting server-rendered DOM right now.

True only during the synchronous mount of hydrate: render what the server rendered, then switch to client-only values from on_settled. Always false on the server and in pages that weren't server-rendered.

element

element(tag: str) -> Callable[..., VNode]

Create a helper fn(*children, **props) -> VNode for any tag name.

Use it for custom elements or tags without a built-in helper:

my_widget = element("my-widget")
my_widget("content", size="large")

Loading

Loading(children: Any = None, *, fallback: Any = None, on: Any = None) -> VNode

Show a fallback while async reads under the boundary aren't ready.

Parameters:

Name Type Description Default
children Any

Content rendered when nothing is loading: a VNode, a zero-arg callable, or a list of either. Async computations read anywhere in this subtree self-register with the boundary when a read raises NotReadyError.

None
fallback Any

VNode, string, or callable returning one of those, shown while any registered async computation has no value.

None
on Any

An accessor, or list of accessors, naming the boundary's inputs. The boundary waits for them initially even if the children never read them, and when one of them changes while data under the boundary is pending, the fallback shows again instead of the old content being held. Use it for "new record, fresh boundary" (a route's id), not for refreshes.

None

Returns:

Type Description
VNode

A component VNode that toggles between

VNode

fallback and children.

Example
Loading(
    lambda: Fragment(Header(), Body()),
    fallback=Spinner(),
    on=user_id,
)

Reveal

Reveal(children: Any = None, *, order: RevealOrder = 'sequential', collapsed: bool = False) -> VNode

Coordinate the reveal order of multiple Loading boundaries.

Each Loading boundary mounted underneath (that isn't nested inside another boundary) registers with the Reveal in mount order, and the Reveal decides when each may show its content and whether it shows its fallback. A Reveal nested inside another is one composite slot in the parent's order: its boundaries stay on their fallbacks until the parent releases the slot, then follow the inner order.

Parameters:

Name Type Description Default
children Any

Content containing one or more Loading boundaries.

None
order RevealOrder

"sequential" (default; contents reveal in DOM order, each waiting for the ones before it), "together" (the whole group reveals at once when every boundary has loaded), or "natural" (each boundary reveals on its own data; useful for a nested group that should count as one slot in the parent without coordinating internally).

'sequential'
collapsed bool

Only consulted with order="sequential". When True, boundaries past the current frontier render nothing instead of their own fallback, so only one fallback shows.

False

Returns:

Type Description
VNode

A component VNode.

Example
Reveal(
    [
        Loading(PanelA(), fallback=p("Loading A...")),
        Loading(PanelB(), fallback=p("Loading B...")),
    ],
    collapsed=True,
)
Note

Every boundary's content mounts immediately (parked off-document while pending), so all of them load in parallel; order only controls when each is revealed.

Portal

Portal(children: Any = None, *, mount: Any = 'body') -> VNode

Render children into a different DOM container.

Matches SolidJS's <Portal mount={...}>. The children mount into mount (by default document.body) while remaining linked to the surrounding component's reactive scope, so signals, context, and lifecycle hooks still apply, and they're removed when the portal unmounts.

Parameters:

Name Type Description Default
children Any

A VNode, a list of VNodes, or a zero-arg callable (rendered as a reactive hole).

None
mount Any

The target container: an Element instance, a CSS selector string, or a kernel node id. Defaults to "body".

'body'

Returns:

Type Description
VNode

A component VNode whose children render inside mount.

Note

A portal renders nothing during a server render. Its children mount in the browser once the page has hydrated.

Example
Show(
    show_modal,
    lambda: Portal(
        div(p("Modal content"), class_="modal"),
        mount="#modal-root",
    ),
)

action

action(fn: Callable[P, Awaitable[R]]) -> Action[P, Future[R]]
action(fn: Callable[P, R]) -> Action[P, R]
action(fn: Callable[..., Any]) -> Action

Wrap a mutation in a transaction.

Calling the returned Action invokes fn and holds a transition open until it settles. Inside:

  • Plain writes stage into the transaction. Signals and stores written by the action (before or after an await) commit to the graph but reveal together when the action settles, as does the landing of any memo it refreshes. Reads inside the action see the staged values.
  • Optimistic writes reveal now. Values written through create_optimistic or create_optimistic_store show immediately and revert when the action settles.
  • action.pending() is True from the call until it settles; affects marks other values as pending, and until waits for a condition on the authoritative view.

Independent actions settle separately. Nested actions and actions that write shared reactive state join and publish together.

Errors route to the nearest Errored boundary captured at call time and re-raise to the awaiter, so await my_action(...) behaves like a normal call.

Parameters:

Name Type Description Default
fn Callable[..., Any]

The mutation; sync or async def.

required

Returns:

Type Description
Action

A callable Action with a tracked .pending accessor.

Example
todos, set_todos = create_store({"items": []})
shown, set_shown = create_optimistic_store(lambda: deep(todos)["items"], [])

@action
async def add_todo(title):
    set_shown(lambda s: s.append({"title": title, "saving": True}))
    saved = await api_create(title)
    set_todos(lambda s: s["items"].append(saved))

affects

affects(*targets: Any) -> None

Declare, inside an action, which values the action is going to change.

Until the action settles, is_pending reports True for expressions that read any of targets, even before the action has written anything. Use it when the pending state should show on the data being changed rather than on the action itself, for example to dim a record while a save is in flight.

Parameters:

Name Type Description Default
*targets Any

Signals, memos, or stores (a store proxy marks the selected subtree).

()

Raises:

Type Description
RuntimeError

If called outside an action's synchronous segment.

TypeError

For a target that isn't a reactive node or store.

Example
@action
async def rename(user_id, name):
    affects(users)
    await api_rename(user_id, name)
    refresh(users)

children

children(fn: Callable[[], Any]) -> ChildrenAccessor

Resolve a component's children once, so it can inspect or reuse them.

Wraps a getter that returns children (typically props.children) and returns a memoized accessor. Nested lists are flattened, reactive children (accessors and zero-argument functions) are called and their results resolved, and None and booleans are dropped. This matches Solid 2.0's children helper.

from wybthon import children as resolve_children


@component
def Tabs(props: ParentProps):
    tabs = resolve_children(props.children)
    return nav(lambda: [li(tab) for tab in tabs.to_array()])

create_effect

create_effect(compute: Callable[..., Any], apply: Callable[..., Any], *, defer: bool = False, error: Callable[[BaseException], Any] | None = None) -> Computation

Create a side effect that re-runs when its tracked sources change.

Effects run after the DOM has been committed, so they observe the updated document; the first run happens on the next flush (right after the component that created it has mounted), not at creation. Inside a component they're disposed on unmount.

Split effect: compute runs tracked and returns a value; apply runs untracked with (value, prev) and performs the side effect. Incidental reads inside apply never over-subscribe the effect, and signal writes belong there. apply may return a cleanup callable that runs before the next apply and on disposal.

Use create_tracked_effect when one callback should combine tracking and side effects. Split effects keep committed resources alive while replacement data is held.

If compute accepts a positional parameter it receives its previous return value (None on the first run). compute may be async def; awaits suspend the effect without blocking and reads after an await are still tracked.

Parameters:

Name Type Description Default
compute Callable[..., Any]

The tracked stage.

required
apply Callable[..., Any]

Untracked side-effect stage receiving (value, prev) (or just (value,) if it declares one parameter). May return a cleanup callable.

required
defer bool

When True, skip the first apply (tracking still starts immediately).

False
error Callable[[BaseException], Any] | None

Optional handler receiving exceptions raised by compute (sync or async) instead of routing them to the nearest Errored boundary.

None

Returns:

Type Description
Computation

The underlying computation; call .dispose() to stop it.

Example
create_effect(count, lambda value, prev: print(prev, "->", value))

create_effect(
    lambda: name(),
    lambda value: (timer := start_timer(value), lambda: stop_timer(timer))[1],
    defer=True,
)
Note

Effects don't run during a server render; see is_server.

create_memo

create_memo(fn: Callable[..., AsyncIterator[T]], *, equals: Any = ..., lazy: bool = ..., unobserved: Callable[[], Any] | None = ..., name: str | None = ..., loading_value: T = ..., ssr_source: SsrSource = ...) -> Memo[T]
create_memo(fn: Callable[..., Awaitable[T]], *, equals: Any = ..., lazy: bool = ..., unobserved: Callable[[], Any] | None = ..., name: str | None = ..., loading_value: T = ..., ssr_source: SsrSource = ...) -> Memo[T]
create_memo(fn: Callable[..., T], *, equals: Any = ..., lazy: bool = ..., unobserved: Callable[[], Any] | None = ..., name: str | None = ..., loading_value: T = ..., ssr_source: SsrSource = ...) -> Memo[T]
create_memo(fn: Callable[..., Any], *, equals: Any = _DEFAULT_EQUALS, lazy: bool = False, unobserved: Callable[[], Any] | None = None, name: str | None = None, loading_value: Any = _MISSING, ssr_source: SsrSource = 'server') -> Memo[Any]

Create a derived value that recomputes when its sources change.

Memos evaluate once at creation unless lazy=True. Updates are pull-based: the body runs when the memo is read after a tracked source changed, and observers are notified only when the new value differs under equals. If fn accepts a positional parameter it receives the previous value (None on the first run).

Async memos. When fn is an async def (or returns an awaitable), the memo becomes an async computation: reading it before the first value raises NotReadyError, which the nearest Loading boundary turns into fallback UI. Once it has a value, a recompute caused by an input change opens a transition: readers keep the previous value and the parts of the UI that depend on the changed input stay as they are until the new value lands, so the screen never shows a new input next to old data. Use is_pending to show a refresh hint and latest to read the new state early. Reads after an await are tracked exactly like reads before it.

Async generators. An async def body containing yield streams: each yielded value becomes the memo's new value. Use it to adapt sockets, subscriptions, or any async iterable.

Parameters:

Name Type Description Default
fn Callable[..., Any]

Zero- or one-arg callable producing the value (sync, async, or an async generator).

required
equals Any

Equality policy; see create_signal.

_DEFAULT_EQUALS
lazy bool

When True, the memo suspends once it loses its last subscriber (and recomputes fresh if read again later). Non-lazy memos live for their owner's lifetime.

False
unobserved Callable[[], Any] | None

Optional callback fired when the memo loses its last subscriber; pair it with lazy=True for resource cleanup.

None
name str | None

Optional label used in dev-mode diagnostics.

None
loading_value Any

For async memos, a value to serve while the first run is in flight instead of raising NotReadyError. The memo is then never "not ready": it doesn't register with Loading, doesn't report pending for its first run, and never holds a transition on mount. Use it for nice-to-have data (a recommendation panel, a badge count).

_MISSING
ssr_source SsrSource

Where an async memo resolves when the page is server-rendered. "server" (the default) resolves it on the server and hydrates the browser with that value; "hybrid" does the same, then always re-runs it quietly in the browser; "client" never runs it on the server, so it loads after hydration. See Server rendering.

'server'

Returns:

Type Description
Memo[Any]

A Memo accessor.

Example
doubled = create_memo(lambda: count() * 2)

async def load_user():
    uid = user_id()          # tracked: refetches when it changes
    return await fetch_json(f"/api/users/{uid}")

user = create_memo(load_user)
suggestions = create_memo(load_suggestions, loading_value=[])

create_optimistic

create_optimistic(source: Accessor[T]) -> tuple[Accessor[T], Setter[T]]
create_optimistic(source: Callable[[], T]) -> tuple[Accessor[T], Setter[T]]
create_optimistic(source: T) -> tuple[Accessor[T], Setter[T]]
create_optimistic(source: Any) -> tuple[Accessor[Any], Setter[Any]]

Create a value whose writes are optimistic.

Reads return the override while one is active, else the source value. Writes reveal immediately, even inside an action whose other writes are held, and the override reverts when the transaction it was written in settles. While an override is active, is_pending reports True for expressions that read it, and until sees through it to the source.

Use it both for optimistic data (the like count you expect the server to confirm) and for process state that must show during an action (a "saving" flag).

Parameters:

Name Type Description Default
source Any

A zero-arg accessor to shadow, or a plain initial value.

required

Returns:

Type Description
tuple[Accessor[Any], Setter[Any]]

A (getter, setter) pair like create_signal.

Example
likes = create_memo(fetch_like_count)          # async source
shown, set_shown = create_optimistic(likes)     # shadows it

@action
async def like():
    set_shown(lambda n: (n or 0) + 1)   # instant UI
    await api_like()
    await refresh(likes)                # real data lands; the override reverts

create_owner

create_owner() -> Owner

Create an ownership scope owned by the current one.

The scope is disposed with its parent. Run code under it with run_with_owner, and dispose it early with owner.dispose(). Pass it to run_with_owner(None, ...) first for a scope with an independent lifetime.

create_reaction

create_reaction(effect: Callable[[], Any], *, error: Callable[[BaseException], Any] | None = None) -> Callable[[Callable[[], Any]], None]

Separate tracking from re-execution: run effect once when tracked reads change.

Returns track(fn). Calling track runs fn and subscribes to what it reads; the first time any of those sources changes, effect runs (untracked, after the DOM commit) and the subscription ends. Call track again to re-arm it. Matches Solid 2.0's createReaction.

track = create_reaction(lambda: print("count changed"))
track(lambda: count())

create_render_effect

create_render_effect(compute: Callable[..., Any], apply: Callable[..., Any], *, defer: bool = False, error: Callable[[BaseException], Any] | None = None) -> Computation

Create a split effect that runs in the render phase, before the DOM commit.

Wybthon's reactive holes and prop bindings are render effects, so a render effect observes the DOM in the same state the framework's own bindings do (updates emitted, not yet committed). Prefer create_effect unless you're building a rendering primitive.

Like Solid 2.0's createRenderEffect, it always takes a tracked compute stage and an untracked apply stage, with the same arguments as create_effect.

create_root

create_root(fn: Callable[[Callable[[], None]], T], *, detached: bool = False) -> T

Run fn inside a root owned by the current scope unless explicitly detached.

Use it for explicitly disposable reactive work. Pass detached=True for global stores or subscriptions that outlive the surrounding component.

Parameters:

Name Type Description Default
fn Callable[[Callable[[], None]], T]

Receives a dispose callable that tears the root down.

required
detached bool

Create an independent lifetime instead of joining the current owner.

False

Returns:

Type Description
T

Whatever fn returns.

create_signal

create_signal(value: T | Callable[[], T], *, equals: Any = _DEFAULT_EQUALS, name: str | None = None, owned_write: bool = False) -> tuple[Accessor[T], Setter[T]]

Create a reactive signal and return its (getter, setter) pair.

Writes are staged: the setter records the new value and every read keeps returning the committed value until the next flush (a browser microtask, the end of an event handler, or an explicit flush). There is no batch(); everything batches.

The setter supports functional updates: pass lambda n: n + 1 and it receives the latest staged value, so repeated updates in one handler compose. To store a callable as the value, return it from an updater: set_fn(lambda _: my_callable).

Function form. When value is a zero-argument callable, the result is a writable derived signal: the getter tracks whatever the function reads and recomputes when those sources change, while the setter overrides the value until the next source change.

Parameters:

Name Type Description Default
value T | Callable[[], T]

The initial value, or a zero-arg function for the derived form.

required
equals Any

Equality policy deciding when subscribers are notified:

  • default: identity fast path, then ==. Re-setting an equal value is a no-op.
  • False: always notify, even for equal values.
  • a callable (old, new) -> bool: skip notification when it returns True. Pass lambda a, b: a is b for identity-only semantics.
_DEFAULT_EQUALS
name str | None

Optional label used in dev-mode diagnostics.

None
owned_write bool

Allow writes from inside an owned scope (a memo, an effect's compute stage, or a reactive hole) without raising WriteInScopeError. Reserve it for state that's genuinely local to that scope, such as a measurement a memo caches for itself.

False

Returns:

Type Description
Accessor[T]

A (getter, setter) tuple. The getter is an

Setter[T]

Accessor (call it to read, .peek() to

tuple[Accessor[T], Setter[T]]

read untracked); the setter is a Setter.

Example
count, set_count = create_signal(0)
set_count(5)
count()                     # 0: staged, not yet visible
flush()
count()                     # 5
set_count(lambda n: n + 1)  # functional update
count.peek()                # 5 (untracked read of the committed value)

doubled, _ = create_signal(lambda: count() * 2)   # derived form

create_tracked_effect

create_tracked_effect(fn: Callable[..., Any], *, error: Callable[[BaseException], Any] | None = None) -> Computation

Run a tracked callback after DOM commit, disposing its scope before each run.

Prefer split effects for external resources: a tracked callback can run while async work is pending. Reads subscribe; put writes in a split effect's apply stage. Doesn't run during a server render.

create_unique_id

create_unique_id() -> str

Return a unique id string (for for/id attribute pairs).

Ids created while a server render or a hydration mounts are derived from the component's position in the tree (wyb-h followed by a short hash), so the server and the hydrating browser agree on them.

flush

flush(fn: Callable[[], T] | None = None) -> T | None

Apply staged writes, run every dirty effect, and commit the DOM now.

Writes become visible to reads only when the graph flushes. In the browser that happens automatically on a microtask and after every event handler; call flush() when you need the settled state now, for example right after a write in synchronous test code.

If the flush opens a transition (a write made an async computation that already had a value recompute), the affected parts of the UI stay on their previous state until that work lands; everything else reveals as usual.

Safe to call at any time; a no-op when nothing is pending, and a no-op inside an effect (the running flush finishes the work).

With fn, runs it first and then flushes, returning its result: flush(lambda: set_count(1)) applies the write synchronously.

Example
count, set_count = create_signal(0)
set_count(1)
count()   # 0: the write is staged
flush()
count()   # 1

get_observer

get_observer() -> Computation | None

Return the computation currently tracking reads, or None.

get_owner

get_owner() -> Owner | None

Return the active ownership scope, or None outside any scope.

Capture it before an await and restore it with run_with_owner when you need to create primitives after the boundary.

is_accessor

is_accessor(value: Any) -> bool

Return True when value is a reactive expression: an Accessor or a zero-arg function.

This is the single rule the framework uses to decide what becomes a reactive hole (as a child), a reactive binding (as a prop), or an auto-unwrapped component prop. Plain functions and lambdas qualify when they take no required positional arguments; bound methods likewise. Classes, Refs, components, and callbacks that take arguments do not.

is_disposed

is_disposed(owner: Owner) -> bool

Return whether owner has been disposed.

is_pending

is_pending(fn: Callable[[], Any]) -> bool

Return True while a change is in flight for the value fn reads.

Evaluates fn in probe mode and reports whether any value it read is waiting on a transition:

  • a signal or memo whose new value is held (the batch that changed it is waiting for async work to land, or the write was made inside an action that hasn't settled);
  • an async computation whose recompute is in flight (a quiet refresh is silent);
  • a value an in-flight action declared with affects;
  • an optimistic value with an active override;
  • a read that raises NotReadyError.

The reads are tracked like any other, so a hole using is_pending updates as the state changes, but they don't make the hole wait for the transition: an indicator has to show during the hold.

span(lambda: "Refreshing..." if is_pending(user) else "")
button("Save", disabled=lambda: is_pending(lambda: store.items))

is_server

is_server() -> bool

Return True while a server render is in progress.

Use it to choose a data source, or to skip browser-only work, in code that also runs in the browser:

async def load_user():
    if is_server():
        return database.users.get(user_id())
    return await fetch_json(f"/api/users/{user_id()}")

latest

latest(fn: Callable[[], T]) -> T | None

Evaluate fn against the newest state, without ever suspending.

Two things differ from a plain read:

  • Not-ready async reads return their most recent value (or None if they never resolved) instead of raising NotReadyError.
  • Values a transition holds return the new value being computed rather than the one the UI still shows. Use it when a piece of UI should update ahead of the rest (a header that shows the newly selected id while the detail pane keeps the old record).

map_array

map_array(source: Callable[[], Sequence[T] | None], fn: Callable[..., U], *, keyed: bool | Callable[[T], Any] = True, fallback: Callable[[], U] | None = None) -> Memo[list[U]]

Map a reactive list to mapped rows with per-row owner scopes.

Rows are matched between updates according to keyed:

  • True (default): match by identity (scalars by value). A matched row keeps its scope and mapped result and its index accessor updates. fn(item, index) receives the raw item and an Accessor[int].
  • False: match by position. The row at each index is reused and its item accessor updates. fn(item, index) receives an Accessor[T] and an int.
  • a callable key(item) -> hashable: match by key. Both item and index are accessors: fn(item, index) receives Accessor[T] and Accessor[int].

Row bodies run untracked inside the row's owner; anything reactive inside a row must read an accessor within a hole, memo, or effect, and cleanups registered with on_cleanup run when the row is removed.

Parameters:

Name Type Description Default
source Callable[[], Sequence[T] | None]

Zero-arg accessor returning the list (or None).

required
fn Callable[..., U]

Row mapping function; see the shapes above.

required
keyed bool | Callable[[T], Any]

Matching strategy.

True
fallback Callable[[], U] | None

Optional zero-arg callable whose result is the single row when the list is empty.

None

Returns:

Type Description
Memo[list[U]]

A Memo yielding the list of mapped rows.

merge

merge(*sources: Any) -> Mapping[str, Accessor[Any]]

Merge prop sources into one reactive mapping; later sources win.

Each source may be a component's props, a plain dict (values may be accessors or static), a zero-arg function returning a dict, or another merged or omitted view. Reads resolve right to left at access time, so tracking flows through to whichever source supplied the key. A key present with the value None overrides earlier sources, as an explicit undefined does in Solid 2.0.

The result is a mapping of accessors: spread it onto an element (button(**merge(defaults, extra))).

Example
attrs = merge({"type": "button"}, {"disabled": props.busy})
button("Save", **attrs)

omit

omit(source: Any, *keys: str | Callable[[str], bool]) -> Mapping[str, Accessor[Any]]

Return a reactive view of source without some keys.

Pass key names, or a single predicate that returns True for each key to drop. The replacement for Solid 1.x's splitProps: handle some props locally and forward the rest.

rest = omit(props, "label", "children")
rest = omit(props, lambda key: key.startswith("on_"))

on_cleanup

on_cleanup(fn: Callable[[], Any]) -> None

Register fn to run when the active scope is disposed or re-runs.

  • Inside an effect body: runs before each re-run and on disposal.
  • Inside a component body: runs when the component unmounts.
  • Inside a reactive hole or For row: runs when that region is re-evaluated or torn down.

Raises:

Type Description
RuntimeError

If called outside any reactive scope.

on_settled

on_settled(fn: Callable[[], Any]) -> None

Run fn once the current render has settled and committed to the DOM.

The replacement for on_mount: inside a component body, fn runs after the flush that mounted the component has finished, so refs are assigned and the DOM is live. fn may return a cleanup callable, which runs when the owning scope is disposed (on unmount).

Doesn't run during a server render.

Parameters:

Name Type Description Default
fn Callable[[], Any]

Zero-arg callback. May return a cleanup callable.

required

Raises:

Type Description
RuntimeError

If called outside any reactive scope.

Example
class ChartProps(Props):
    data: Prop[list[float]]


@component
def Chart(props: ChartProps):
    canvas = Ref()

    def start():
        handle = draw(canvas.current, props.data.peek())
        return lambda: handle.destroy()

    on_settled(start)
    return canvas_(ref=canvas)

prop

prop(*, default: T) -> Prop[T]
prop(*, default_factory: Callable[[], T]) -> Prop[T]
prop(*, default: Any = _MISSING, default_factory: Callable[[], Any] | None = None) -> Any

Declare a Prop[T] field's default.

Type checkers only recognize a field specifier's default when it's passed by keyword, so write prop(default=0). Use default_factory for mutable defaults.

class CounterProps(Props):
    step: Prop[int] = prop(default=1)
    tags: Prop[list[str]] = prop(default_factory=list)

refresh

refresh(target: Any) -> Awaitable[Any]

Recompute a derived read quietly and return an awaitable for its settled value.

"Quiet" means no pending state is reported while the run is in flight: is_pending stays False and readers keep the previous value. Use it after a server write to re-ask for data derived from the source of truth.

Called inside an action, the refreshed value lands into the action's transaction and reveals together with the action's other writes when it settles.

Parameters:

Name Type Description Default
target Any

A Memo (including async memos and function-form signals) or a derived store / projection.

required

Returns:

Type Description
Awaitable[Any]

An awaitable resolving with the target's next settled value.

Awaitable[Any]

Safe to ignore for fire-and-forget use.

repeat

repeat(count: Callable[[], int] | int, fn: Callable[[int], U], *, start: Callable[[], int] | int = 0, fallback: Callable[[], U] | None = None) -> Memo[list[U]]

Map an integer range to rows, each with its own owner scope.

The counterpart of Solid 2.0's repeat: fn(i) runs once per slot for i in range(start, start + count). Growing the count maps only the new slots; shrinking it disposes the removed ones. Changing start maps every slot again, because each row received a plain int.

Parameters:

Name Type Description Default
count Callable[[], int] | int

The number of slots, or an accessor for it.

required
fn Callable[[int], U]

Maps one slot number to a row.

required
start Callable[[], int] | int

The first slot number, or an accessor for it.

0
fallback Callable[[], U] | None

Optional zero-arg callable whose result is the single row when the count is zero.

None

Returns:

Type Description
Memo[list[U]]

A Memo yielding the list of mapped rows.

resolve

resolve(fn: Callable[[], T]) -> Awaitable[T]

Return an awaitable for the next settled value of fn().

Tracks every dependency of fn, including cached memos, and waits for pending or quiet async recomputations. Streams become ready at their first fresh yield. Rejects when the expression raises; cancellation removes the temporary subscriptions.

user = create_memo(fetch_user)
data = await resolve(user)

run_with_owner

run_with_owner(owner: Owner | None, fn: Callable[[], T]) -> T

Run fn under owner (pass None for no ownership) and return its result.

until

until(pred: Callable[[], T], *, timeout: float | None = None) -> _Settle[T]

Return an awaitable for the settled, truthy value of pred().

pred is evaluated reactively against the authoritative view: optimistic overrides are invisible, so the condition observes real data only. Inside an action, reads see the action's own staged writes. A read that raises NotReadyError simply waits for the value.

Parameters:

Name Type Description Default
pred Callable[[], T]

Zero-arg predicate.

required
timeout float | None

Optional seconds after which the awaitable rejects with TimeoutError.

None
Example
@action
async def checkout(cart_id):
    order_id = await api_checkout(cart_id)
    refresh(orders)
    await until(lambda: any(o["id"] == order_id for o in orders()))
    navigate(f"/orders/{order_id}")

untrack

untrack(fn: Callable[[], T]) -> T

Run fn without tracking any reactive reads.

Use it to read a signal inside an effect without depending on it, or to seed local state from a prop during component setup without subscribing (the dev-mode top-level-read warning is silenced inside untrack; Accessor.peek is the shorter form for a single read).

Parameters:

Name Type Description Default
fn Callable[[], T]

Zero-arg callable to invoke with tracking suppressed.

required

Returns:

Type Description
T

Whatever fn returns.

hydrate

hydrate(vnode: Any, container: Element | str | int) -> Root

Adopt server-rendered HTML under container and make it interactive.

The counterpart of render for pages rendered by wybthon.server. The tree mounts exactly as render would mount it, except that each node claims the matching node the server already put in the document instead of creating a new one. Async values the server resolved come from the page's data-wyb-state script, so they're neither fetched again nor shown as loading. Input recorded by the production bootstrap before hydration is replayed afterward.

Mismatches never break the page: a node that doesn't match is created in place, server nodes nobody claimed are removed, and dev mode logs a warning.

Parameters:

Name Type Description Default
vnode Any

The same root view the server rendered.

required
container Element | str | int

The element holding the server-rendered markup: an Element, a CSS selector, or a kernel node id.

required

Returns:

Type Description
Root

A Root, as from render.

Example
from wybthon import hydrate

hydrate(App(), "#app")

render

render(vnode: Any, container: Element | str | int) -> Root

Render a tree into a container element.

Mounts vnode under container, commits every buffered DOM op in one bridge crossing, and registers the container as an event delegation root. Rendering into the same container again patches the existing tree in place.

Parameters:

Name Type Description Default
vnode Any

The root VNode (or a component call, string, list, or reactive expression; anything a component may return).

required
container Element | str | int

An Element, a CSS selector for an existing DOM node, or a kernel node id.

required

Returns:

Type Description
Root

A Root; call .dispose() to tear the app down.

Example
from wybthon import render
from wybthon.html import h1

root = render(h1("Hello, world!"), "#app")

get_request_event

get_request_event() -> RequestEvent | None

Return the request being server-rendered, or None in the browser.

http_header

http_header(name: str, value: str, *, append: bool = False) -> None

Declare a response header while the calling scope is alive (server only).

Parameters:

Name Type Description Default
name str

The header name (case-insensitive).

required
value str

The header value.

required
append bool

Add another value instead of replacing existing ones (for set-cookie, say).

False

http_status

http_status(code: int, text: str | None = None) -> None

Declare the response status while the calling scope is alive (server only).

The most recent live declaration wins. When the declaring scope is disposed before the head is committed, its declaration is removed and the status falls back to the latest one still alive.

Parameters:

Name Type Description Default
code int

The HTTP status code.

required
text str | None

An optional reason phrase.

None

Fragment

Fragment(*args: Any) -> VNode

Group children without adding a wrapper element.

Fragments mount their children directly into the parent between two empty comment markers, so they never pollute selectors like :first-child or affect layout.

Parameters:

Name Type Description Default
*args Any

Children. Lists and tuples are flattened.

()

Returns:

Type Description
VNode

A _fragment VNode.

Example
Fragment(h1("Title"), p("Body text"))

h

h(tag: str | Callable[..., Any] | None, props: PropsDict | None = None, *children: Any) -> VNode

Create a VNode from a tag, props, and children.

The low-level constructor behind the helpers in wybthon.html. Reactive expressions in child positions are wrapped as holes when the parent mounts; components receive their children verbatim through the children prop.

Parameters:

Name Type Description Default
tag str | Callable[..., Any] | None

An HTML tag name, a special tag ("_text", "_hole", "_fragment"), or a component.

required
props PropsDict | None

Mapping of prop names to values. May be None.

None
*children Any

Children to attach. Lists and tuples are flattened.

()

Returns:

Type Description
VNode

A new VNode.

Example
from wybthon import h

view = h("button", {"on_click": handle_click}, "Click me")

hole

hole(getter: Callable[[], Any], *, key: str | int | None = None) -> VNode

Create a reactive hole explicitly, optionally with a key.

Reactive expressions in child positions become holes automatically; reach for hole() when you need a stable key for keyed reconciliation inside a fragment, or to make a hole visually explicit.

The expression may return a VNode, a string, a list of either, or None.

Parameters:

Name Type Description Default
getter Callable[[], Any]

Zero-arg reactive expression evaluated inside its own render effect.

required
key str | int | None

Optional stable identity for keyed reconciliation.

None

Returns:

Type Description
VNode

A _hole VNode.

Example
div(hole(lambda: f"Hello, {name()}!"))
div(lambda: f"Hello, {name()}!")   # equivalent

create_optimistic_store

create_optimistic_store(source: Any, initial: Any = None) -> tuple[Any, Callable[[Any], None]]

Layer optimistic draft edits over a reactive authoritative source.

Active edits replay in submission order when the source changes. They are removed when their submitting action settles; other actions keep their edits. Draft callbacks must be deterministic: replay mustn't perform network or other side effects.

create_projection

create_projection(fn: Callable[..., Any], initial: Any = None) -> Any

Create a read-only derived store with memo readiness and refresh semantics.

create_store

create_store(initial: list[T], seed: None = None) -> tuple[StoreList[T], StoreSetter[DraftList[T]]]
create_store(initial: S, seed: None = None) -> tuple[Store[S], StoreSetter[Draft[S]]]
create_store(initial: Callable[..., Any], seed: Any = None) -> tuple[Any, StoreSetter[Any]]
create_store(initial: Any, seed: Any = None) -> tuple[Any, StoreSetter]

Create a reactive mapping or sequence and its atomic draft setter.

A deriving function may return data or mutate its draft, synchronously or asynchronously. Its reads are tracked and its writes are staged together.

deep

deep(value: Any) -> Any

Return a detached snapshot, tracking changes only within this subtree.

reconcile

reconcile(data: Any, key: str | Callable[[Any], Any] | None = 'id') -> Any

Prepare replacement data, preserving matched list entity identities.

key names the field that identifies list entities ("id" by default), or is a function computing the identity from an entity. key=None replaces list entities positionally. Duplicate keys are matched in occurrence order, without assigning the same entity to multiple rows.

snapshot

snapshot(value: Any) -> Any

Return a detached, untracked copy of the currently visible state.

Public API by area

Everything below is importable with from wybthon import .... Module pages linked in the left column carry the details.

Area Names
Components component, Component, Props, ParentProps, Prop, prop, merge, omit, children, ChildrenAccessor
VDOM VNode, h, hole, Fragment, element, is_accessor
Reactivity Accessor, Setter, Signal, Memo, Owner, create_signal, create_memo, create_effect, create_tracked_effect, create_render_effect, create_reaction, create_root, create_owner, is_disposed, create_unique_id, flush, on_settled, on_cleanup, untrack, get_owner, get_observer, run_with_owner, map_array, repeat, WriteInScopeError
Async NotReadyError, ServerError, is_pending, latest, refresh, resolve, action, Action, affects, until, create_optimistic
Stores Store, StoreList, StoreSetter, Draft, DraftList, DraftExpiredError, create_store, create_projection, create_optimistic_store, reconcile, snapshot, deep
Flow control Show, For, Repeat, Switch, Match, dynamic, client_only
Boundaries Loading, Reveal, Errored, Portal, lazy
Context Context, ContextNotFoundError, create_context, use_context
DOM and events Element, Ref, DomEvent, EventHandler, event
Rendering render, hydrate, Root, is_server, is_hydrating, NoHydration, Hydration
Server requests RequestEvent, ResponseHead, get_request_event, http_status, http_header
Dev mode is_dev_mode, set_dev_mode
HTML helpers One helper per HTML element (div, p, button, input_, ...), plus element

Other modules are imported by name and aren't re-exported:

Module Contents
wybthon.router Router, Route, RouteProps, Outlet, Link, navigate, current_path, use_params, use_query, use_hash, use_base_path, preload, QueryParams, resolve, RouteSpec
wybthon.forms form_state, FormState, Field, the bind_* helpers, validators, and ARIA helpers
wybthon.server render_to_string, render_to_stream, RenderStream
wybthon.testing render, Screen, fire, cleanup, and the in-memory DOM
wybthon.svg SVG element helpers
wybthon.virtual, wybthon.scheduling Virtual lists and cooperative scheduling
wybthon.reactivity Everything above plus Computation and Transition
from wybthon import Prop, Props, button, component, create_signal, div, p, prop, render


class CounterProps(Props):
    initial: Prop[int] = prop(default=0)


@component
def Counter(props: CounterProps):
    count, set_count = create_signal(props.initial.peek())
    return div(
        p(t"Count: {count}"),
        button("+1", on_click=lambda: set_count(lambda n: n + 1)),
    )


root = render(Counter(initial=5), "#app")
# later: root.dispose()

Browser versus CPython

Reactivity (including async memos, actions, and flush), stores, forms, context, flow control, and VDOM construction (VNode, h, Fragment, the HTML helpers) all run in plain CPython, which is how the unit tests work. Touching a real DOM (render into a page, Element queries, navigate with history) needs Pyodide, or the in-memory DOM of wybthon.testing.

See also