Component libraries
A component library lets you publish reusable Citry components without choosing the application's engine. The package defines components once, and each application installs them into its own Citry instance.
Use a library when components need to travel as a Python package. For components that belong to one application, ordinary registration is simpler.
Create the package
A small library can keep its definitions together and publish one manifest:
acme-ui/
pyproject.toml
src/
acme_ui/
__init__.py
py.typed
components/
__init__.py
badge.py
Add Citry as a normal package dependency. Include file-backed templates, JavaScript, and CSS in the built distribution as package data.
Define a library component
Subclass LibraryComponent instead of Component. It has the same component authoring API, but defining it does not register it with an engine.
# src/acme_ui/components/badge.py
from citry import LibraryComponent
class AcmeBadge(LibraryComponent):
class Kwargs:
label: str
tone: str = "neutral"
template = """
<span class="badge badge--{{ tone }}">
{{ label }}
</span>
"""
css = """
.badge {
border-radius: 999px;
padding: 0.25rem 0.6rem;
}
"""
Do not set citry on a library definition. Citry creates a separate concrete component class for every engine that installs the library.
Publish the manifest
List the definitions in a ComponentLibrary, in the order Citry should register them:
# src/acme_ui/__init__.py
from citry import ComponentLibrary
from acme_ui.components.badge import AcmeBadge
__citry_library__ = ComponentLibrary(
name="acme-ui",
components=(AcmeBadge,),
)
Construct the manifest after decorators have finished changing the component classes. Creating it seals the definitions against later top-level class attribute changes. Objects stored inside an attribute are not deeply frozen, so library code should treat the entire definition as immutable after this point.
If the components rely on a custom extension, declare its exact name:
__citry_library__ = ComponentLibrary(
name="acme-ui",
components=(AcmeBadge,),
required_extensions=("acme_theme",),
)
Installation fails before publishing any component if a required extension is missing.
Use the library catalog in the editor
The Citry VS Code extension accepts the manifest itself as its registry target:
{
"citry.app": "acme_ui:__citry_library__"
}
The language server creates a library-only registry containing Citry's built-ins and the manifest's components. It does not include host-application components, configuration, or host-provided extensions. If the manifest has a custom required_extensions entry, expose a configured Citry instance that installs the library and select that instance instead.
Install and use the library
Applications pass either the package or its manifest to register_library():
import acme_ui
from citry import Citry
app = Citry()
installed = app.register_library(acme_ui)
The component now works like any other registered component:
<c-acme-badge label="Ready" tone="success" />
Installation is atomic for state owned by Citry. If validation, a name collision, or a registration hook raises, Citry restores its component and library registries. It cannot undo outside effects performed by package imports or extension hooks, such as writing a file or changing another global.
Register the same manifest again and Citry returns the existing installation. To install a changed or reloaded generation with the same name, clear the engine first and perform normal application startup again.
Compose a library component from Python
Calling a library definition stores the inputs until an active engine is known:
from citry import Component
from acme_ui import AcmeBadge
class Receipt(Component):
class Kwargs:
status: str
citry = app
def template_data(self, kwargs: Kwargs, slots):
return {
"badge": AcmeBadge(
label=kwargs.status,
tone="success",
),
}
template = """
<p>Status: {{ badge }}</p>
"""
When Citry inserts badge, it resolves the call through Receipt's engine. Outside a component tree, pass the engine explicitly:
badge = AcmeBadge(label="Ready")
html = str(badge.render(citry=app))
Library calls retain the definition's broad Python signature rather than generating an exact call signature from Kwargs. Component input validation still runs when the call is resolved.
Other Python objects can take part in this contextual conversion too. See Custom component values.
Access the installed component class
Most code should use the template tag or call the library definition. When you need the concrete class bound to one engine, use the installation handle:
Badge = installed[AcmeBadge]
html = str(Badge(label="Ready"))
An installation handle becomes stale after Citry.clear() or when another generation replaces it. Accessing its classes then raises LibraryInstallationStale instead of returning a class that no longer belongs to the active registry.