Using situ¶
situ compiles a Python component into a JavaScript island, so
interactive state can be written once, in Python, and sited explicitly — Local (client only),
Url (shareable, server re-renders), Server (a command), Synced.
This system works with it in two ways.
Styling situ_ui widgets¶
If you use situ_ui, load the bridge after ui.css:
situ.css retargets situ_ui's --ui-* tokens at ours, so both stacks share one palette without
reimplementing situ_ui's 168 class bodies. Forty lines of aliases rather than ~600 lines of
duplication.
It deliberately does not map metrics. situ_ui is dense enterprise on purpose — 13px base type, 32px controls — and ours is 14px. That is a product decision rather than a vocabulary mismatch, so the bridge leaves it alone. The opt-in block at the end of the file adopts our metrics too, if you want them.
Writing islands with our classes¶
The reference application has six. The important constraint:
Macros cannot be used inside a situ component
situ compiles a component's .html at source level, so a binder that a Jinja macro emits
is never seen by the compiler. Macros render server-side chrome; islands are hand-written
against the class vocabulary.
<div class="ads-card">
<div class="ads-card__body ads-stack">
<input class="ads-input" :bind="query" placeholder="Search…">
<ul class="ads-list">
{% for row in rows %}
<li :show="query == '' or query in t.name">
<a class="ads-list__link" :attr="aria-current: 'true' if open_id == t.id else False"
@click="open_id = t.id">{{ row.name }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
False, not '', to remove an attribute
:attr removes an attribute on False or None. An empty string is how True renders — a
present boolean attribute — so else '' produces aria-current="", which is present and
matches [aria-current]. Three wizard steps once lit up at once for exactly this reason.
Region swaps and vendor libraries¶
A Url-sited change re-renders the region, which destroys and recreates any DOM a vendor
library owns. Rebuild on the swap:
The global is window.__siting, and afterSwap is the page-level hook — onSwap belongs to the
island and is a different thing.
Keyboard traps in vendor editors¶
A rich-text field is the commonest way an application fails WCAG 2.1.2. Quill binds Tab to
inserting a tab character, so focus goes in and never comes out:
keyboard: { bindings: {
tab: { key: "Tab", handler: () => true },
"shift-tab": { key: "Tab", shiftKey: true, handler: () => true },
} }
Returning true means I did not handle this, so the browser's own Tab runs. Also load content
with quill.setContents(…, "silent") — without it the change event steals focus on page load.