Ludic Logo
Tests StatusCode CoveragePython 3.12Checked with mypyDiscord Server


"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 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.

The framework is in a very early development/experimental stage. There are a lot of half-functioning features at the moment. Contributions are welcome to help out with the progress!


  • 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



Here is a table comparing Ludic to other similar tools:

HTML renderingServer SideClient SideClient Side
Uses a template engineNoNoNo
UI interactivity</> htmx*ReactReact
Backend frameworkStarlette*FastAPIFastAPI
Client-Server CommunicationHTML + RESTJSON + RESTWebSockets

(*) HTMX as well as Starlette 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()

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:

def Counter(number: int) -> Cluster:
    return Cluster(
            disabled=number <= 0
            hx_get=app.url_path_for("Counter", number=max(0, number - 1)),
        b(number, style={"font-size": "2em"}),
            hx_get=app.url_path_for("Counter", number=number + 1),



Python 3.12+


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:paveldedik/ludic-template



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.
Made with Ludic and HTMX and 🐍 • DiscordGitHub