Introduction
"I've just composed my first PageLayout component and I have no words!"
– Igor Davydenko
Ludic is a lightweight framework for building HTML pages with a component approach similar to React. It is built to be used together with htmx.org so that developers don't need to write almost any JavaScript to create dynamic web services. Its potential can be leveraged together with its web framework which is a wrapper around the powerful Starlette framework. It is built with the latest Python 3.12 features heavily incorporating typing.
Features
#- Seamless </> htmx integration for rapid web development in pure Python
- Type-Guided components utilizing Python's typing system
- Uses the power of Starlette and Async for high-performance web development
- Build HTML with the ease and power of Python f-strings
- Add CSS styling to your components with Themes
- Create simple, responsive layouts adopted from the Every Layout Book
Comparison
#Here is a table comparing Ludic to other similar tools:
Feature | Ludic | FastUI | Reflex |
---|---|---|---|
HTML rendering | Server Side | Client Side | Client Side |
Uses a template engine | No | No | No |
UI interactivity | </> htmx* | React | React |
Backend framework | Starlette, Django* | FastAPI | FastAPI |
Client-Server Communication | HTML + REST | JSON + REST | WebSockets |
(*) HTMX as well as Starlette or Django are optional dependencies for Ludic, it does not enforce any frontend or backend frameworks. At it's core, Ludic only generates HTML and allows registering CSS.
Quick Example
#Here is a simple re-implementation of an example from Reflex (although only for natural numbers):
The counter can be included on any page like here:
from typing import override from ludic.html import b from ludic.web import Endpoint, LudicApp, Request from ludic.catalog.buttons import ButtonDanger, ButtonSuccess from ludic.catalog.layouts import Box, Cluster app = LudicApp() @app.get("/") def index(request: Request) -> Box: return Box(Counter(0))
Note that the Box
is just a component wrapping the buttons and the number to make it nicely framed. You can read more about the Box
in the Layouts section later. Anyway, the Counter
component is the part that is more interesting:
@app.get("/counter/{number:int}") def Counter(number: int) -> Cluster: return Cluster( ButtonDanger( "Decrement", disabled=number <= 0 hx_get=app.url_path_for("Counter", number=max(0, number - 1)), hx_target="#counter", ), b(number, style={"font-size": "2em"}), ButtonSuccess( "Increment", hx_get=app.url_path_for("Counter", number=number + 1), hx_target="#counter", ), id="counter", )
Requirements
#Python 3.12+
Installation
#pip install "ludic[full]"
As similar for Starlette, you'll also want to install an ASGI server:
pip install uvicorn
You can also use the cookiecutter template to quickly create a new project:
cookiecutter gh:getludic/template
Integrations
#Here is a list of integrations and a link to the guide on how to get started:
Contributing
#Any contributions to the framework are warmly welcome! Your help will make it a better resource for the community. If you're ready to contribute, read the contribution guide on GitHub.
- GitHub Issues – If you encounter a bug, please report it here.
- GitHub Discussions – To request a new feature, this is the best place to initiate the discussion.
- Discord – Join our Discord server for support, sharing ideas, and receiving assistance.