Component
The base class every component subclasses.
Component class
Base class for all Citry components.
A component is a reusable unit of UI defined by:
- A template (Citry template syntax)
- Optional typed inputs (via inner
Kwargs,Slotsclasses) - A data method that maps inputs to template variables
Subclass this to define your own components. At minimum, set template (inline string) or template_file (path to file).
class_id attribute
class_id: strStable import-derived identity shared by reloads of this component path.
The read-only value is suitable for routes and cross-process logical identity. Combine it with Citry.engine_id and definition_id when retained metadata must match one exact live class generation in the current process.
definition_id attribute
definition_id: strOpaque process-lifetime identity of this exact component class object.
The read-only value exists before class-created extension hooks run. An alias or re-registration preserves it, while defining a replacement class creates a different value even when class_id remains the same.
citry attribute
citry: CitryThe Citry instance that owns this component class.
Defaults to the module-level default instance. Set this inside the class body to assign a component to a specific instance. The binding cannot be changed or deleted after the class is defined. A subclass of a concrete component uses the same owner; define a fresh component tree when another engine needs its own copy.
transparent attribute
transparent: boolWhether this component's output joins the surrounding component's serialization frame.
A transparent component is structural rather than visual: its rendered output gets no data-cid-<id> marker and is not framed as a child component at serialize time. Used by built-ins like <c-provide> that only wrap content. Hooks, the render id, and dependency merging behave the same as for any component.
name attribute
name: str | NoneOverride the name under which this component is registered.
By default, the class name is used (lowercased + kebab-case). Set this to register under a specific name instead::
class MyWidget(Component):
name = "fancy-widget"
# registered as "fancy-widget", not "mywidget" / "my-widget"
template attribute
template: str | NoneInline template string (Citry template syntax).
Mutually exclusive with template_file. Read the loaded template with get_template().
template_file attribute
template_file: str | NonePath to a template file. Mutually exclusive with template.
Resolved relative to the directory of the class that declares the value first, then relative to the owning component's Citry(dirs=...) entries; absolute paths are used as-is. A subclass that inherits this declaration therefore keeps the declaring class's file location. A plain mixin can declare the path while the component still supplies the owning engine.
js attribute
js: str | NoneInline primary JS for this component. Mutually exclusive with js_file. Read the loaded content with get_js().
js_file attribute
js_file: str | NonePath to the component's primary JS file. Mutually exclusive with js. Resolved like template_file.
css attribute
css: str | NoneInline primary CSS for this component.
Mutually exclusive with css_file. Citry adds these selectors to the page exactly as written, so they can style any matching element. Use class names specific to the component to avoid styling something else by accident. Values returned by css_data() become custom properties for one rendered use of the component. Read the loaded content with get_css().
css_file attribute
css_file: str | NonePath to the component's primary CSS file. Mutually exclusive with css. Resolved like template_file.
Kwargs attribute
Kwargs: type | NoneOptional typed keyword arguments.
Define as a plain class with type annotations. The metaclass combines it with parent component declarations and converts the result to a dataclass (with slots) automatically::
class Card(Component):
class Kwargs:
title: str
body: str = ""
Slots attribute
Slots: type | NoneOptional typed slot definitions, inherited like Kwargs.
Use SlotInput for places where people can add content. A field without a default must be filled whenever the component is used. The required attribute on <c-slot> checks something different: it raises an error only if Citry renders that tag without content.
State attribute
State: type | NoneOptional typed values that survive between server event calls.
Define State as a plain nested class with type annotations. The Events extension combines inherited declarations and converts the result to a mutable, slotted dataclass automatically::
class Search(Component):
class State:
query: str = ""
page: int = 1
State must contain only JSON-serializable values. By default, every field is readable and writable in the browser. Use _public to choose which fields the browser may read and _model to choose which public fields it may change. _storage is "signed" by default and may be set to "server". _max_bytes defaults to 8192 bytes, and _max_age accepts a datetime.timedelta or None for no expiry.
Citry starts State from same-named keyword arguments and field defaults. Define state_data(self, kwargs, slots) when the values need to be derived instead. Assign State = None on a subclass to stop inheriting its parent's State declaration.
TemplateData attribute
TemplateData: type | NoneOptional typed template data output, inherited like Kwargs.
JsData attribute
JsData: type | NoneOptional typed schema for the js_data() output. Like TemplateData, it inherits through component C3 and a plain annotated class converts to a dataclass.
CssData attribute
CssData: type | NoneOptional typed schema for the css_data() output. Like TemplateData, it inherits through component C3 and a plain annotated class converts to a dataclass.
id attribute
id: strUnique render ID for this component instance.
A fresh ID is minted every time a CitryElement is rendered, so the same CitryElement rendered twice produces two distinct IDs.
kwargs attribute
kwargs: AnyThe resolved keyword arguments.
If the component defines a Kwargs dataclass, this is an instance of that class. Otherwise, a plain dict.
raw_kwargs attribute
The keyword arguments as a plain dict, even if a Kwargs dataclass is defined. Useful when you need dict access regardless of typing.
slots attribute
slots: AnyThe resolved slot fills, with every value normalized to a Slot.
If the component defines a Slots dataclass, this is an instance of that class. Otherwise, a plain dict.
raw_slots attribute
The slot fills as a plain dict of Slot values, even if a Slots dataclass is defined. Useful when you need dict access regardless of typing.
parent attribute
parent: Component | NoneThe component that wrote this one into its template. None for a root component, and for one rendered standalone (e.g. an element handed into an expression as {{ element }}).
The link follows authorship, not slot placement: a component written inside a <c-fill> keeps the fill's author as its parent, no matter whose slot the content lands in. (This differs from Vue, whose $parent points at the slot host.) To ask "what am I rendered inside, slots included", use provide/inject, which travels the render path and crosses slot boundaries.
root attribute
root: ComponentThe component at the top of the parent chain (the same authorship rule as parent).
For root components, self.root is self. Never None.
template_data function
Return the template variables.
By default this returns kwargs, so a component's inputs are usable in its template without an override: a Kwargs field named title is available to the template as {{ title }}. Override this to map the inputs to a different set of variables. The returned value may be a dict, a NamedTuple, or the typed TemplateData instance, and a declared TemplateData validates it either way; the result is what the template's expressions see.
A returned variable wins over a template_globals entry of the same name, so an input shadows a same-named global (globals act as defaults). Unlike js_data and css_data, which stay opt-in and return None by default, template variables never cross into the browser: they only make names resolvable to the template's own expressions.
Parameters
kwargsAny- The keyword arguments passed to the component.slotsAny- The slot fills passed to the component.
Returns
dict[str, Any] | None: A mapping of template variables. Defaults to the component's
js_data function
Return the JS variables for this render.
Override this to expose per-render data to the component's JS (Component.js). The dict is serialized to JSON and delivered to the component's $component callback in the browser; identical data is sent to the browser only once, however many instances share it. Consumed by the built-in dependencies extension.
Parameters
kwargsAny- The keyword arguments passed to the component.slotsAny- The slot fills passed to the component.
Returns
dict[str, Any] | None: A dict of JS variables, or None for no variables.
css_data function
Return the CSS variables for this render.
Override this to expose per-render values to the component's CSS (Component.css) as CSS custom properties: a returned {"row-color": "red"} is usable in the CSS as var(--row-color), scoped to this component's elements. Identical data across renders shares one generated stylesheet. Consumed by the built-in dependencies extension.
Keys are custom-property name suffixes, without the leading --. Values must be strings, finite numbers, or None. Citry escapes quoted strings and rejects names or raw values that could escape the generated declaration. It checks structural containment, while the browser remains responsible for full CSS value grammar and whether a value is valid for the property that consumes it.
Parameters
kwargsAny- The keyword arguments passed to the component.slotsAny- The slot fills passed to the component.
Returns
dict[str, Any] | None: A dict of CSS variables, or None for no variables.
on_dependencies function
on_dependencies(scripts: list[Dependency], styles: list[Dependency]) -> tuple[list[Dependency], list[Dependency]] | NoneHook to adjust this component's JS/CSS tags before they enter the page.
Called at serialize time, once per rendered instance of this component, with the Script/Style entries this component contributes (its Dependencies entries and its own Component.js/css). Return a (scripts, styles) pair to replace the lists, mutate them in place, or return None (the default) to keep them. Removing the component's own script entries can break the component's behavior in the browser; this hook is for adding attributes, reordering, or dropping entries you know are provided elsewhere.
To adjust the page-wide lists instead (every component's tags, after de-duplication), implement an extension with an on_dependencies method (see citry.ext.dependencies.OnDependenciesContext).
on_render function
on_render() -> RenderReplacement | OnRenderGenerator | NoneHook to replace or post-process this component's rendered output.
Called on every render of this component, after template_data and just before the template renders. Return None (the default) to render the template as usual. Return content to use it as the component's whole output instead; the template is then not rendered at all. Accepted content:
- a
str, used as-is (NOT autoescaped: it is this component's own output, the same trust as its template) - a composed element (
OtherComponent(title="hi")), rendered in this component's place - an already-rendered
CitryRender, inlined - a
Slot, invoked with no data - a
ComponentLike, resolved against this component's Citry instance
Because None means "no replacement", return "" to output literally nothing.
Everything the hook needs is on self: kwargs, slots, parent, inject(). To pass data to the template, use template_data; this hook is for replacing output.
For example, render a placeholder instead of the template when there is no data::
class MyTable(Component):
template = "<table>...</table>"
def on_render(self):
if not self.raw_kwargs.get("rows"):
return "<p>No data</p>"
return None
Generator form. Include a yield to also see the component's finished output, children included, and react to it - for example to catch a failing child (this is how error boundaries work)::
class Guarded(Component):
template = "..."
def on_render(self):
# BEFORE: runs just before the template renders.
result, error = yield
# AFTER: result is the completed CitryRender, or None
# if rendering failed (then error is the exception).
if error is not None:
return "<p>Something went wrong</p>"
return None
The protocol:
- A bare
yield(oryield None) on the first yield means "render my template as usual"; yielding content means "use this as my output instead" (same accepted values as above). - The yield receives
(result, error)once that output has fully settled:resultis the liveCitryRender(not a string; do not serialize it here unless you are replacing the output with the serialized form), orNonewhen rendering failed, witherrorset. Exactly one of the two is set. - You can yield any number of times; each
yield <content>replaces the output, renders it, and receives the new(result, error). A bareyieldafter the first answers immediately with the current result unchanged. - End with
return <content>to set the final output,raiseto make that the component's error, or plainreturnto keep the current result (an unhandled error keeps bubbling).
provide function
Make data available to this component's descendants.
Any component rendered below this one (including components inside slot content rendered below it) can read the data with self.inject(key). The data does NOT enter the template variables; descendants opt in explicitly.
Call this from template_data. The data is frozen into an immutable payload at this point, so what descendants inject always has exactly the fields given here, as attributes::
class Page(Component):
template = '<c-user-card />'
def template_data(self, kwargs, slots):
self.provide("user_data", user=kwargs["user"])
return {}
class UserCard(Component):
template = '<div>{{ name }}</div>'
def template_data(self, kwargs, slots):
return {"name": self.inject("user_data").user}
In templates, the same thing is written with the <c-provide> built-in component: <c-provide key="user_data" c-user="user">.
Parameters
inject function
Read data a component above this one provided under key.
The data must have been provided by a component on the render path above this one (via Component.provide or the <c-provide> built-in); the nearest provider wins when the same key is provided twice. A component's own provide calls are visible to its descendants only, never to its own inject.
The returned payload is immutable, with the provided fields as attributes: self.inject("user_data").user. Works during template_data and keeps working after the render for as long as the component instance is kept.
Parameters
unprovide function
unprovide(key: str) -> NoneHide an inherited provide from this component's descendants.
The component may still inject the inherited value itself. Components rendered below it observe the key as missing unless a nearer component provides a new value under the same key. Call this from template_data when content below a component boundary must establish a fresh context before using a compound child.
Parameters
keystr- The provide key to hide below this component.
ancestors attribute
All ancestor components, nearest first: the parent, then the parent's parent, up to and including the root. Empty for a root component.
Useful to check where a component sits, e.g.::
is_themed = any(isinstance(c, Theme) for c in self.ancestors)
The chain follows who wrote the component, the same as parent: a component written inside a <c-fill> has the fill's author as its parent, not the component whose slot rendered it. So the check above holds when Theme's own template renders this component; for "am I rendered inside a Theme, slots included", have Theme provide a value and inject it here, which travels the render path and crosses slot boundaries.
get_template function
get_template() -> CitryTemplate | NoneThe loaded template (a CitryTemplate), or None for a template-less component. Resolved from template / template_file once per class; on_template_loaded applied.
get_js function
get_js() -> str | NoneThe loaded primary JS content, or None. Resolved from js / js_file once per class; on_js_loaded applied.
get_css function
get_css() -> str | NoneThe loaded primary CSS content, or None. Resolved from css / css_file once per class; on_css_loaded applied.
get_dependencies function
get_dependencies() -> CitryDependenciesThe merged secondary assets from this component's (and, per Dependencies.extend, its bases') nested Dependencies class. Owned by the built-in dependencies extension.
reset_template function
reset_template() -> NoneClear this class's loaded template (and its compiled form and cached Const optimization results), so the next render re-reads it. Subclasses that inherit this template cache their own copies; reset them too (Citry.get_components_for_file lists every class using a given file).
reset_files function
reset_files() -> NoneClear this class's loaded JS/CSS (and, via the on_files_reset hook, extension state such as the merged Dependencies), so the next access re-reads them.