Traps
The gotchas, condensed — above all the silent failures, the ones that do not crash but simply do not work.PEP 649 — State fields do not register
SymptomA State looks correct but its fields are empty / the re-render does not fire. No error at all.
FixPut `from __future__ import annotations` FIRST in every file that declares a State. Without it, the annotations are not evaluated as expected and the metaclass does not see the fields.
A binding read outside a render scope
Symptom`state.field.set(...)` produces nothing useful; the binding behaves like a raw value.
FixReading a ClientState field returns a `ClientBinding` ONLY inside a render scope (`@page` / `@refreshable`). Inside a handler, it is the raw value. Driving the state from a handler → mutate, do not bind.
@validator returning nothing
SymptomThe field is `None` after writing.
FixA `@validator("field")` receives `(self, value)` and MUST `return` the value (transformed or not). `def _n(self, v): v.strip()` writes `None` — the `return` is missing.
Passing a ClientBinding to a non-bindable prop
Symptom`ComponentUsageError` at construction (that one is loud, and good).
FixOnly the props listed in `BINDABLE_PROPS` accept a binding. For something dynamic on variant/size/color → a conditional render on the server (`color="error" if failed else "success"`). See the Catalogue for the per-component list.
a native for instead of ui.each on stateful components
SymptomAfter a re-render/reorder, open overlays close, checkboxes lose their state.
FixIterate with `ui.each(items, key="id")` as soon as the body produces stateful components: the key stabilises identity across the morph. A Python `for` is enough for static content (`ui.text` × N).
A bindable prop whose attribute does not act on the root
SymptomThe binding exists but the UI does not react (e.g. `disabled` on a `<div>` wrapper, `src` on a `<span>`).
FixThe attribute must land on the right carrier element. Declare `BINDABLE_CARRIERS = {prop: "bz-ref"}` (or `forward_binding`) to forward the directive onto the child that really carries the attribute.
Writing name= / @click / hx-post by hand
SymptomIt works, but it is the escape hatch — not the idiom.
FixThe `name` is auto-derived from the binding (`AUTONAME_FROM`). Actions go through `on_<event>=` (a server callable or a client string). If you find yourself writing raw `hx-`, it is probably a runtime primitive that is missing.