Component JavaScript and CSS
A reusable component can bring the behavior and styles it needs. Put its JavaScript in js and its CSS in css. Citry collects those assets when the component renders and includes each one once in the finished page.
This page covers assets owned by one component. For libraries and shared files, see Dependency files. To control where the collected tags appear, see Place JavaScript and CSS.
Add behavior and styles
This chart sends its points and height from Python to the browser:
from citry import Component
class Chart(Component):
class Kwargs:
points: list[int]
height: str = "240px"
class JsData:
chart_points: list[int]
class CssData:
chart_height: str
def js_data(
self,
kwargs: Kwargs,
slots,
) -> JsData:
return self.JsData(chart_points=kwargs.points)
def css_data(
self,
kwargs: Kwargs,
slots,
) -> CssData:
return self.CssData(chart_height=kwargs.height)
template = """
<div class="chart"></div>
"""
js = """
$component(({ els, data }) => {
const chartPoints = data.chart_points;
drawChart(els[0], chartPoints);
});
"""
css = """
.chart {
height: var(--chart_height);
}
"""
$component() registers a callback for each rendered Chart. Its els value contains the component's root elements. Its data value is what that render returned from js_data().
Code outside $component() runs once when the component script loads. Keep page-wide setup there. Put code that reads one rendered component's elements or data inside the callback:
console.log("The chart script loaded");
$component(({ els, data }) => {
console.log("One chart is ready", els, data);
});
Citry wraps classic component JavaScript in a self-executing function. Its top-level variables therefore stay private to that script.
Send data to JavaScript
js_data() returns the data for one render. Citry serializes it as strict JSON, seeds its top-level keys into that render's Alpine scope, and gives a fresh instance-local graph to that render's $component() callback when one exists.
The returned mapping must follow these rules:
- every key is an exact
str; - every value is JSON-serializable;
- numbers are finite, so
NaNand infinity are rejected.
An Alpine expression can read those keys directly, so $component() is not required only to copy server data into scope. A render with neither Alpine expressions nor a live $component() call does not send the data. Identical payloads are transported once, but nested arrays and objects are not shared between instances.
Python names normally use snake_case. Assign them to camelCase names when browser code keeps using them:
const chartPoints = data.chart_points;
That small distinction makes it easier to see which language owns a name.
Send data to CSS
css_data() turns one render's values into CSS custom properties. A returned {"chart_height": "240px"} is available as var(--chart_height) inside that component.
CSS data follows a narrower contract:
- every key is an exact
strand a valid custom-property suffix; - values are strings, finite numbers, or
None; - booleans and structured values are rejected.
Citry quotes and escapes strings with spaces, unless the value starts with a CSS function such as calc(...) or rgba(...). It also rejects values that could break out of the generated declaration, such as a top-level semicolon, an unmatched block, or a `