Quickstart¶
A working counter in three files: a component pair plus a one-call mount. There is no build step and no Node toolchain to install.
1. The component — two sibling files, one stem¶
A situ component is two files sharing a stem, so each gets native editor tooling: .py files are real Python (type-checked by your checkers), .html files are real HTML.
count declares its site: Local means the browser owns this value. Because bump touches only local state (no await), the compiler classifies it as a client handler and compiles its body to JavaScript. The global count line is ordinary CPython — it names the signal the handler writes, which also keeps the file valid for the type checkers.
2. The mount¶
from pathlib import Path
import situ
from litestar import Litestar
from litestar.plugins.jinja import JinjaTemplateEngine
from litestar.static_files import create_static_files_router
from litestar.template.config import TemplateConfig
from situ import mount_static_component
HERE = Path(__file__).parent
app = Litestar(
route_handlers=[
mount_static_component(
path="/counter",
stem=HERE / "counter", # counter.py + counter.html
template="page.html", # situ ships a minimal default
meta={"name": "Counter"},
),
# serve the runtime shim the generated island loads from /static/_rt.js
create_static_files_router(path="/static", directories=[situ.static_dir()]),
],
template_config=TemplateConfig(
directory=situ.templates_dir(), engine=JinjaTemplateEngine
),
)
3. Run it¶
What just happened¶
The mount compiled the pair once and derived two routes:
| Route | Serves |
|---|---|
GET /counter |
the rendered page, with a #siting-signals bootstrap ({"local": {"count": 0}, ...}) |
GET /counter/island.js |
the generated island — @click became a listener, bump's body became a compiled function, :text became a subscribed DOM update |
Open /counter/island.js in the browser: it is a few kilobytes of readable JavaScript with a header that says exactly what it is. The page also loads the shared shim _rt.js (signal store + wire mechanics, ~500 lines, no expression interpreter) — everything app-specific is generated.
Two rules the runtime enforces
- Every reactive element (anything carrying a binder or event) must live inside
<header>or the single<div data-region>— those are the two roots where binders are wired at boot. data-regionmust be the first attribute on that<div>.
Break either and the element simply isn't bound; keep the rules and server re-renders can never orphan a listener. See Components.
Where next¶
- Build a complete app, one concept per step: the Tutorial.
- Give a signal to the database: Server components.
- What can go in a handler or a binder expression: The compiled dialect.
- All the
:/@shorthand: Binders & events.