Alpine in templates
Use Alpine when part of a component should respond immediately in the browser. A click can open a panel, an input can update a preview as someone types, and a button can change local state without a request to Python.
Citry includes Alpine.js and starts it when the rendered page needs it. Write Alpine attributes directly on the HTML inside a component. Do not add another Alpine script tag.
Add a browser-side counter
This counter changes as soon as someone clicks the button:
<div x-data="{ count: 0 }">
<button type="button" @click="count += 1">
Add one
</button>
<output x-text="count"></output>
</div>
The three Alpine attributes divide the work:
x-datacreates the browser valuecount.@clickchanges it after a click.x-textinserts its current value into<output>.
Citry notices Alpine's standard x-, @, and : attributes in the rendered HTML and includes its owned browser runtime. The interaction does not need a Component.js block or a separate JavaScript entry file. Browser behavior added only through JavaScript should use a $component() callback.
Read the common Alpine forms
Alpine directives begin with x-. Its two common shorthands make browser events and bound attributes easier to read:
| Form | Job |
|---|---|
x-data="{ open: false }" | Create local browser data. |
x-show="open" | Show an element when an expression is truthy. |
x-text="label" | Insert text into an element. |
x-model="query" | Keep a form control and a value in step. |
@click="open = true" | Short for x-on:click="open = true". |
:disabled="busy" | Short for x-bind:disabled="busy". |
Modifiers stay part of the attribute name. For example, @keydown.enter.prevent="submit()" runs only for Enter and prevents the browser's default action.
Use the Alpine documentation{: target="_blank" rel="noopener"} for its complete directive, modifier, and magic API.
Write JavaScript in Alpine attributes
An Alpine attribute contains a JavaScript expression. A Citry c-* attribute contains a Python expression:
<section
c-class="{'has-results': results}"
x-data="{ open: false }"
>
<button type="button" @click="open = !open">
Toggle details
</button>
<p x-show="open">Details</p>
</section>
Python decides the class while rendering the component. Later, Alpine owns open in the browser and changes it without rendering the component again. Python template names are not automatically available to Alpine, and Alpine names are not automatically available to Python. js_data() is the explicit way to expose Python-produced browser data.
Do not put {{ ... }} inside an Alpine expression. A static attribute keeps those braces as literal text. When Alpine needs a starting value from Python, pass it deliberately.
Seed Alpine from Python
Return per-instance browser data from js_data(). Citry seeds every top-level key into this component's Alpine scope before any Alpine expression runs:
from citry import Component
class Counter(Component):
class Kwargs:
start: int = 0
class JsData:
count: int
def js_data(
self,
kwargs: Kwargs,
slots,
) -> JsData:
return {"count": kwargs.start}
template = """
<div>
<button type="button" @click="count += 1">
Add one
</button>
<output x-text="count"></output>
</div>
"""
The returned data must be JSON-serializable: strings, numbers, booleans, None, lists, and string-keyed dictionaries made from those values. Use JavaScript naming in that object, such as itemCount, even when its Python source is named item_count.
Add $component only when the component also needs JavaScript setup, managed effects, client props, or additional callback-owned scope data. The Add browser behavior tutorial builds that path one step at a time.
Keep Alpine data inside its component
HTML nested inside the same component follows Alpine's normal scope rules. An active nested Citry component starts a separate component scope, so its own template does not silently inherit the parent's x-data values.
An Alpine event handler on a child component tag is a deliberate exception. The caller owns the expression, and Citry attaches the handler to the child's rendered roots:
<section x-data="{ selected: false }">
<c-ActionButton @click="selected = true" />
<p x-show="selected">Selected</p>
</section>
Other Alpine attributes on a component tag are ordinary Python component inputs. This does not copy x-show to the child's root:
<c-Panel x-show="open" />
Put the directive on a plain wrapper, or let the child accept an explicit attribute mapping. To pass reactive values or callbacks across the component boundary, use $c-props.
Choose the right kind of loop
Alpine's x-for, x-if, and x-teleport work with browser-owned HTML inside an active component. For example, Alpine can repeat a plain <li>:
<ul x-data="{ items: ['Ocean', 'Forest'] }">
<template x-for="item in items">
<li x-text="item"></li>
</template>
</ul>
Do not use a native Alpine structural directive to clone an active Citry component. The clone would not receive a new Citry identity, State, client props, or lifecycle. Use server-side <c-for> and <c-if> around Citry component tags instead.
Read Alpine runtime for the complete structural-directive and deployment limits.
Use Citry values from Alpine
When a component declares State, its Alpine expressions can read and write it with $state. When it declares server events, Alpine can use the related status and event magics:
$loading()and$error()$sendEvent()and$onEvent()
Inside any active Citry component, Alpine can use the rendered-context magics $provide(), $inject(), and $unprovide().
The Browser APIs reference gives the exact contract for each name. Continue to Client interactivity for component-owned JavaScript, reactive client props, handlers, and lifecycle.
When the Citry language server knows the owning component, it treats unknown free Alpine variables as errors by default. Names from JsData, x-data, an enclosing x-for, synchronous $component scope writes, Alpine/Citry magics, browser globals, and configured lint-only variables are included automatically. The application or component lint policy can reduce unknown names to warnings or ignore them for a deliberately open integration.
TODO - Alpine attrs on Component
Not only event handlers, and our $c-props.
TODO Alpine attrs on HTML elements
All allowed. @c- events and $c-props.