<!-- 1. Import Python Components --> <!-- @import Button from ../lib/maddex/Button.py --> <!-- 2. Keep one authored parent root element --> <section class="space-y-4"> <div class="flex gap-2 mb-4"> <x-button variant="outline" size="sm">Tasks: todos.length</x-button> </div> <!-- 3. Reactive Loop --> <ul> <template pp-for="todo in todos"> <li key="todo.id" class="p-2 border-b">todo.title</li> </template> </ul> <script> // 4. State initialized by Python backend automatically const [todos, setTodos] = pp.state([[todos|json]]); </script> </section>
HTML-first also means one authored parent tag per route or component. Keep @import comments above that root, and keep any owned <script> inside it.
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.
<x-rocket class="text-primary 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
<!-- Keep one root wrapper in index.html --> <div class="space-y-3"> <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> </div>