A tutorial series, DEV.to blog series — from your first HTML element to production-grade User Interfaces, all in pure Python.
Modern Python web frameworks force developers into a split workflow: business logic lives in Python files with full IDE support, while presentation logic is exiled to template files that offer none of it. Template languages like Jinja2 introduce their own syntax for conditionals, loops, and variable access — syntax that your linter cannot check, your type checker cannot verify, and your debugger cannot step through. Every context variable passed across that boundary is a potential KeyError waiting to surface at runtime. Probo eliminates this divide entirely by making HTML a native Python construct — written, validated, and refactored with the same tools you already use for the rest of your codebase.
PART 1: Introduction to Probo — Write HTML Entirely in Python
What is Probo?
Probo is a Python-first, declarative UI rendering framework. Instead of writing HTML in .html files or using template languages like Jinja2, you write everything in pure Python.
No template files. No string concatenation. No f-strings full of angle brackets. Just Python functions and classes that are your HTML.
The Two Flavors of Every Tag
Every HTML tag in Probo comes in two forms:
| Flavor | Example | Returns | Use Case |
|---|---|---|---|
| Function (lowercase) |
div(), h1(), p()
|
Rendered HTML | Quick rendering, lightweight |
| Class (uppercase) |
DIV(), H1(), P()
|
SSDOM tree node | Tree manipulation, streaming |
from probo import div, DIV
# Function: returns a string immediately
# return_list=True
html_string = div("Hello World",)
# → "<div>Hello World</div>"
# Class: returns a tree node, call .render() to get the string
node = DIV("Hello World",Id="main-title")
# Because it's a Node, you can manipulate it dynamically
node.add(div("Subtitle added later!"))
html_string = node.render()
# → '<div id="main-title">Hello World<div>Subtitle added later!</div></div>'
Note: by adding return_deque=True or return_list=True the output changes from string to respective return type
Built-in Security
Probo auto-escapes all text content by default via markup_escape. If a user submits <script>alert('xss')</script>, Probo renders it as harmless escaped text — no XSS vulnerabilities out of the box.
from probo import p
# User-submitted content is automatically escaped
user_input = '<script>alert("hacked")</script>'
safe_html = p(user_input)
# → "<p><script>alert("hacked")</script></p>"
What You'll Learn in This Series
By the end of this series, you'll be able to:
- Build full HTML pages in Python
- Navigate and manipulate a server-side DOM tree
- Bind dynamic data without prop-drilling
- Run a web server with routing, caching, and streaming
- Build interactive UIs with HTMX — with minimal to no JavaScript required
- Create reusable, skinnable component systems
- Deploy with Gunicorn or Uvicorn
Let's get started! 👇
Top comments (0)