BaseComponent¶
Base class for defining reusable UI components with Pydantic validation and Jinja2 templating.
Class¶
BaseComponent¶
Subclasses are automatically registered and can be rendered using their corresponding HTML/Jinja templates. Components support nested composition.
Fields¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id |
str |
No | auto-generated (pjx-<n>) when omitted |
Unique identifier for the component instance |
BaseComponent is strict by default (model_config = ConfigDict(extra="forbid")): passing an undeclared kwarg at construction time raises a validation error. To accept pass-through attributes, opt a class into pydantic's own extra="allow":
component()-synthesized classless wrappers and {#def#}-less templates are generated with that same config, which is why they accept extras out of the box.
Methods¶
render()¶
Render this component to a finished HTML string.
The template is auto-discovered based on the component class name: a colocated <snake_case_class_name>.pjx file next to the module that defines the class (e.g. MyButton looks for my_button.pjx). All component fields are available in the template context, and nested components are rendered recursively. Subclasses with no adjacent template inherit the nearest ancestor's template and assets through the MRO (first found per kind); a class may have at most one concrete component base — multiple concrete bases raise TypeError at definition time (see Component guide).
session defaults to the session bound by the active request_scope(), or — outside any scope — to a fresh RenderSession the free render() function builds.
render() returns one component's markup and nothing else — it never appends out-of-band swaps and takes no reactivity arguments. ReactiveComponent does not override it either: reactive state lives on the session, and the dependency walk that turns a handler return into a body with OOB legs attached belongs to pyjinhx.responses.compose() — see Response composition and the Reactive API.
Returns: The component's rendered markup as a finished HTML string.
Inside a pjx-adapted FastAPI route, return the component — not .render()
render() returns markup only; it never injects the htmx/pjx.js runtime. In a route wired with setup(app), return the component instance itself so the backend's response composer can fan out and inject the runtime. Calling .render() and returning the resulting string bypasses that path — FastAPIBackend.to_response() only calls inject_runtime() when the result isinstance(..., BaseComponent), so a str return is assumed to be a fragment of an already-booted page and the page ships with no runtime. See Response composition and #938.
render() remains correct everywhere else — tests, non-FastAPI usage, fragments, and any other integration that doesn't route handler returns through the composer.
component¶
Reference an html-only component — a template that has no hand-written Python class — from Python. Returns a BaseComponent subclass bound to that template, so you can instantiate, nest, and render it like any declared component.
from pyjinhx import component
Card = component("Card") # finds card.pjx under the registered components root
Card(title="Hi", content="body").render()
The template is resolved by the same tag -> class registry used for <Card/> tags in templates (pyjinhx.discovery): if "Card" is already registered (a hand-declared class, or a previous component("Card") call), that class is returned as-is. Otherwise component() walks the template directory for card.pjx, parses its {#def#} prop header if it has one (building a validated BaseComponent subclass), or falls back to a permissive extra="allow" placeholder when it doesn't, and registers the result under the tag. setup(components_root=...) (or a prior call to discovery.build_registry(...)) establishes the template directory that walk searches; pass template_dir explicitly to component() to search elsewhere instead.
Arbitrary attributes are accepted (extra="allow") and children map to the content slot, e.g. component("Card")(title="Hi", content="body").
namemust be PascalCase (so it round-trips as<Name/>in templates) — otherwiseValueError.- Idempotent and non-shadowing: if a class is already registered under
name(previously synthesized, or a real declared component), that class is returned —component("Card")twice returns the same object, and it never replaces a declared component. - A missing template raises
LookupErrorwhencomponent()is called.
Because the returned class is registered, it also resolves as <Card/> inside other templates.