Skip to content

Component

wybthon.component

component

@component decorator and forward_ref for function components.

Wybthon's component model is fully reactive and runs once:

  • The body of an @component function executes a single time when the component mounts.
  • Each parameter declared in the function signature receives a reactive accessor (a zero-argument callable). Calling the accessor returns the current prop value and tracks it as a reactive dependency.
  • Embedding an accessor directly inside a VNode tree turns the surrounding region into a fine-grained reactive hole that updates only the relevant DOM when the prop changes.
  • The decorator also enables a direct call style: invoking the component with keyword arguments yields a VNode instead of running the body, so trees can be authored ergonomically.

Authoring modes:

  • Named accessor mode: used when the signature has zero args, kwargs with defaults, or **kwargs. Each parameter becomes a reactive accessor for the prop of the same name.
  • Proxy mode: used when the signature has exactly one positional-only or positional-or-keyword parameter with no default and no *args/**kwargs. The single parameter receives the full ReactiveProps proxy.
Example

A trivial greeting and a counter::

@component
def Greet(name="world"):
    return p("Hello, ", name, "!")

@component
def Counter(initial=0):
    # ``untrack`` snapshots the seed value without subscribing
    # (otherwise we would re-seed on every parent update, and
    # dev mode would warn about a destructured prop).
    count, set_count = create_signal(untrack(initial))
    return div(
        p("Count: ", count),
        button("+", on_click=lambda e: set_count(count() + 1)),
    )

When you need the underlying ReactiveProps proxy (e.g., to iterate keys or forward unknown props), call get_props from inside the component body, or declare the component with a single positional parameter (proxy mode).

Components are expected to return a VNode. Use dynamic for explicit reactive holes when an entire subtree needs to swap based on a signal. A callable return is also accepted (it is wrapped in a single-root reactive hole) but the canonical style is "return a VNode and embed dynamic(...) where you need reactive swaps".

Functions:

Name Description
component

Decorate a function as a Wybthon component.

forward_ref

Create a component that forwards a ref prop to a child element.

component

component(fn: Callable[..., Any]) -> Callable[..., Any]

Decorate a function as a Wybthon component.

The body of fn is invoked once per mount. Each declared parameter is bound to a reactive accessor: call it to read the current value (tracked) or pass it directly into a VNode tree to create a reactive hole.

The decorated callable also supports a direct call style: Counter(initial=5) returns a VNode (equivalent to h(Counter, {"initial": 5})) so component composition feels natural.

See the module docstring for the complete authoring guide and the full mode-selection table.

Parameters:

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

The function to decorate. Its signature determines whether named-accessor mode or proxy mode is used.

required

Returns:

Type Description
Callable[..., Any]

A wrapped callable. When called by the reconciler with a single

Callable[..., Any]

props dict, it executes fn with the appropriate accessors;

Callable[..., Any]

when called by user code with kwargs, it returns a VNode.

forward_ref

forward_ref(render_fn: Callable[..., Any]) -> Callable[..., Any]

Create a component that forwards a ref prop to a child element.

The wrapped function receives (props, ref) instead of (props,), where ref is the value of the ref prop (or None). ref is stripped from props (matching React's forwardRef semantics), so the wrapped function only sees its own concerns.

Parameters:

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

A callable taking (props, ref) and returning a VNode subtree.

required

Returns:

Type Description
Callable[..., Any]

A component callable that forwards the ref prop to render_fn.

Example
FancyInput = forward_ref(lambda props, ref: input_(
    type="text", ref=ref, class_="fancy",
))

my_ref = Ref()
h(FancyInput, {"ref": my_ref})

@component

Decorator that turns a function into a Wybthon component with fully-reactive props.

Each parameter is a reactive accessor, a zero-arg callable.

  • Pass it directly into the tree to create a reactive auto-hole.
  • Call it (name()) to read the current value (tracked when called inside an effect or hole).
  • Wrap with untrack for a snapshot read.
from wybthon import component, p

@component
def Greeting(name="world"):
    # ``name`` is a getter; passing it as a child becomes a reactive hole.
    return p("Hello, ", name, "!")

Stateful components create signals during setup and embed reactive holes (signal getters or dynamic(lambda: ...)). The body runs once; seed local state from props using untrack:

from wybthon import component, create_signal, div, p, span, untrack

@component
def Counter(initial=0):
    count, set_count = create_signal(untrack(initial))
    return div(p("Count: ", span(count)))

Children is a normal prop, also a reactive accessor. Most layouts read children once at setup; wrap with untrack:

from wybthon import component, h3, section, untrack

@component
def Card(title="", children=None):
    kids = untrack(children) if callable(children) else children
    if kids is None:
        kids = []
    if not isinstance(kids, list):
        kids = [kids]
    return section(h3(title), *kids, class_="card")

For memoized, reactive resolution of children (Solid-style), use the children(fn) helper with get_props(). See Reactivity.

Direct calls with keyword arguments return a VNode:

Counter(initial=5)
Card("child1", "child2", title="My Card")

The component still works with h():

h(Counter, {"initial": 5})

Proxy mode

When the component declares a single positional parameter with no default, the decorator passes the underlying ReactiveProps proxy directly:

@component
def DumpProps(props):
    # ``props.x`` -> reactive accessor; ``props.x()`` -> current value.
    return p(dynamic(lambda: ", ".join(sorted(list(props)))))

Use get_props from inside any kwarg-style component to obtain the same proxy.

forward_ref

forward_ref(render_fn) creates a component that receives a ref prop and forwards it to a child element.

The wrapped function receives (props, ref) instead of (props,), and ref is stripped from props (matching React's forwardRef semantics).

from wybthon import forward_ref, h

FancyInput = forward_ref(lambda props, ref: h("input", {"type": "text", "ref": ref, "class_": "fancy"}))

h(FancyInput, {"ref": my_ref, "placeholder": "Type here..."})