Skip to content

Migrating from Solid

Wybthon is essentially SolidJS for Python. Most of the primitives have direct equivalents and the mental model is identical: components run once, signals drive fine-grained updates, and the ownership tree manages cleanup.

The differences are mostly cosmetic: Python instead of JavaScript, builder functions instead of JSX, and a few naming conventions to keep things idiomatic.

API mapping

SolidJS Wybthon
createSignal(initial) create_signal(initial)
createEffect(fn) create_effect(fn)
createMemo(fn) create_memo(fn)
createResource(source, fetcher) create_resource(source, fetcher)
createContext(default) / useContext create_context / use_context
<Show when={...} fallback={...}> Show(when=..., fallback=...)
<For each={...}> For(each=..., children=...)
<Index each={...}> Index(each=..., children=...)
<Switch> / <Match> Switch / Match
<Dynamic component={...} /> Dynamic(component=...)
<Portal mount={...}> create_portal(mount=...)
<ErrorBoundary fallback={...}> ErrorBoundary(fallback=...)
<Suspense fallback={...}> Suspense(fallback=...)
lazy(() => import(...)) lazy(load=...)
onMount(fn) on_mount(fn)
onCleanup(fn) on_cleanup(fn)
batch(fn) batch(fn)
untrack(fn) untrack(fn)
on(deps, fn) on(deps, fn)
createStore(initial) create_store(initial)
produce(fn) produce(fn)

Templates

Solid uses JSX. Wybthon uses Python builders from wybthon.html:

function Greeting(props) {
  return <p>Hello, {props.name}!</p>;
}
from wybthon import component
from wybthon.html import p

@component
def Greeting(name):
    return p("Hello, ", name, "!")

Tag helpers are defined for every standard HTML element. For custom elements, use h directly.

Props

Solid props are reactive getters on a proxy object. Wybthon props arrive as callables:

@component
def Card(title, body):
    return div(h2(title), p(body))

You can pass title straight through (creating a reactive hole) or read title() inside an effect. Destructuring (assigning the value to a local) freezes it at mount, just like Solid.

For ergonomic prop manipulation Wybthon offers get_props (analogous to Solid's splitProps):

from wybthon import get_props

@component
def Button(label, **rest):
    props, others = get_props(rest, ["disabled"])
    return button(label, disabled=props["disabled"], **others)

Signals and effects

Identical in spirit and behavior:

count, set_count = create_signal(0)
create_effect(lambda: print("count =", count()))

create_effect re-runs whenever signals it tracked during the previous run change. There is no manual dep array.

Stores

from wybthon import create_store, produce

state, set_state = create_store({"count": 0, "items": []})

# atomic update:
with produce(state) as draft:
    draft["count"] += 1
    draft["items"].append("new")

Stores wrap nested data in lazy proxies so reads are tracked at the leaf level, exactly like Solid.

Routing

from wybthon import Route, Router, Link

routes = [
    Route(path="/", component=Home),
    Route(path="/users/:id", component=User),
]

@component
def App():
    return Router(routes=routes)

Wybthon's router supports nested routes, dynamic params, query parsing, and lazy components; see Routing.

What's intentionally different

  • Naming. snake_case across the API (create_signal, not createSignal). Component names stay PascalCase.
  • Imports. Pull from wybthon (and optionally wybthon.html for tag helpers).
  • Dynamic. Use dynamic(lambda: ...) to inline a reactive computation; component-style Dynamic exists too.
  • JS interop. Use pyodide.ffi to talk to the host. See Pyodide guide.

What carries over directly

  • The mental model (components run once, fine-grained reactivity).
  • Ownership semantics: on_cleanup attaches to the current owner.
  • Transitions and resources: create_resource integrates with Suspense.
  • Patterns like keyed lists, conditional flows, and nested boundaries.

Next steps