Components
Caspian components are just Python functions. They encapsulate structure, logic, and styling in one place. Start with simple string-based components, then scale into type-safe, reactive, and async server-aware UI patterns.
1. The Basics
A standard component handles prop merging and returns an HTML string. Use
merge_classes to ensure
Tailwind classes don't conflict.
Developer Speed Tip
Don't write boilerplate. If you have the VS Code Extension installed, just type:
caspcom
+
Tab
It automatically generates the standard structure seen below.
2. Type-Safe Props
Want TypeScript-like validation? Use Python's
Literal
type. Your editor will autocomplete variants, and linter will catch typos.
3. HTML Templates & Reactivity
For complex layouts or reactive state, separate your code into an HTML
file. Use render_html to bridge
them.
from casp.component_decorator import component, render_html @component def Counter(**props): return render_html("Counter.html", **props)
<div> <h3>[[label]]</h3> <!-- Prop --> <button onclick="inc()"> {count} <!-- State --> </button> </div>
Passing Context Dictionaries
When you need precise control over variable names, or when passing state
handlers that don't map 1:1 to props, you can pass a dictionary explicitly
as the second argument to render_html.
4. Async Components
Components now support
async
and
await
when your UI needs to wait for server-side work before rendering. This
follows a familiar FastAPI-style pattern: keep simple components
synchronous, and switch to async only when the component needs to await
I/O such as a database query, an external API call, file loading, or
other asynchronous operations.
Mental Model
Think of PulsePoint components like FastAPI route functions: use a normal function for fast static work, and use an async function when you need to await something. This keeps your code clean, explicit, and easy to scale.
When to Use Async Components
Use an async component when the render phase depends on data that is not immediately available. A few common examples:
- Awaiting a database query before building the final HTML.
- Loading related records such as users, posts, notifications, or products.
- Fetching data from an internal API or service layer.
- Reading files or remote resources needed at render time.
If the component only formats props, merges classes, or returns static markup, keep it synchronous. Async is powerful, but it should be used only where waiting is actually required.
from casp.component_decorator import component, render_html @component async def ProfileCard(user_id: str) -> str: # Wait for data before rendering the UI user = await get_user_by_id(user_id) # Return a normal component template once the data is ready return render_html(__file__, { "user": user })
<div class="rounded-xl border border-border p-4 bg-card text-card-foreground"> <h3 class="text-lg font-semibold">{user.name}</h3> <p class="text-sm text-muted-foreground">{user.email}</p> </div>
FastAPI-Style Example
The pattern is intentionally familiar. In FastAPI, you write
async def
when a route needs to await I/O. PulsePoint components now work the
same way. The component itself becomes async, awaits the required
operation, and then returns the final rendered markup.
Best Practice
Keep data fetching inside the async component only when that data is truly part of the component's render contract. If the data belongs to a page-level workflow, load it at the page level and pass it down as props. This keeps components reusable and avoids unnecessary coupling.
Sync vs Async
Use Sync Components
For static layout, prop formatting, class merging, simple template composition, and any component that does not need to wait for I/O.
Use Async Components
For database reads, service calls, file access, remote fetches, or any render path that depends on awaited server-side data.
5. Server-Side Logic (RPC)
Components can also host server-side logic using the
@rpc
decorator. This allows you to encapsulate business logic directly within
the component file.
Important Note
RPCs in components are Global. Unlike Pages (which are scoped to the URL), component RPCs are registered by their function name. Ensure unique names to avoid conflicts.
6. Component Composition
Much like React, PulsePoint allows you to easily compose custom
components together, passing down props and state handlers.
Crucial Rule: Every component HTML template must return a
single parent element. The props intended for reuse
within the component should be mapped directly to this root element using
the
[[propName]]
syntax.