<!-- 1. Import Python Components --> <!-- @import { Badge } from ../components/ui --> <div class="flex gap-2 mb-4"> <Badge variant="default">Tasks: {todos.length}</Badge> </div> <!-- 2. Reactive Loop --> <ul> <template pp-for="todo in todos"> <li key="{todo.id}" class="p-2 border-b">{todo.title}</li> </template> </ul> <script> // 3. State initialized by Python backend automatically const [todos, setTodos] = pp.state([[todos|json]]); </script>
We removed the complexity of the modern stack (Node, Webpack, API Routes) but kept the power by building on FastAPI.
Logic runs in native Async Python. Leverage the entire FastAPI ecosystem: Dependency Injection, Pydantic validation, and Starlette middleware. No JavaScript backend required.
Need complex libraries? We seamlessly integrate Vite. Import from NPM, write TypeScript, and bundle automatically.
Just create
app/users/index.py. We handle the mount points automatically.
Define your schema once. Get auto-generated, type-safe Python clients. Zero SQL boilerplate.
Don't waste time hunting for SVGs. We integrated ppicons (based on Lucide) directly into the framework. Auto-completion, tree-shaking, and zero configuration.
Beautifully designed components that you can copy and paste into your apps. Accessible, type-safe, and built in native Python. No installation fatigue.
from casp.components import component from casp.utils import cn @component def Button( variant="default", size="default", **props ): """ Standard button component with variant support. """ classes = cn( "inline-flex items-center justify-center rounded-md", "text-sm font-medium transition-colors", "focus-visible:outline-none disabled:opacity-50", { "bg-primary text-primary-foreground hover:bg-primary/90": variant == "default", "bg-destructive text-destructive-foreground": variant == "destructive", "h-10 px-4 py-2": size == "default", } ) return f'<button class="{classes}" {...props} />'
We don't force you to write HTML inside Python strings for everything. Use Python Components for atomic, reusable logic. Use HTML Files for complex layouts.
from casp.html_attrs import get_attributes, merge_classes from casp.component_decorator import component @component def Rocket(**props): # 1. Handle Tailwind Merging incoming_class = props.get("class", " ") final_class = merge_classes("w-6 h-6", incoming_class) # 2. Process Attributes (onclick, id, etc.) attributes = get_attributes({ "class": final_class }, props) # 3. Return Pure HTML String return f''' <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" {attributes}> <path d="M4.5 16.5c-1.5..."></path> <path d="m12 15-3-3..."></path> </svg>'''
Perfect for Icons, Buttons, and Badges.
merge_classes
ensures your Tailwind classes never conflict, and
get_attributes
automatically handles prop spreading.
Don't get stuck in "String Hell". For complex UI like Dashboards or Cards, just point your component to an HTML file.
<Rocket class="text-red-500 animate-bounce" />
Call Python functions directly from your HTML events. Caspian handles the serialization, security, and async execution transparently.
@rpc(require_auth=True) async def like_post(post_id: str): # 1. Direct Prisma DB Access (Async) post = await prisma.post.update( where={'id': post_id}, data={'likes': {'increment': 1}} ) # 2. Return data directly to frontend return post.likes
<button onclick="likePost()" > Like Post </button> <script> async function likePost() { // 3. Magic RPC call (over Websocket/HTTP) const newCount = await pp.rpc("like_post", { post_id: "123" }); // 4. Update reactive state setLikes(newCount); } </script>