-
Notifications
You must be signed in to change notification settings - Fork 1
Declarative HTML Elements
HTeaLeaf provides a declarative Python API for building HTML pages — no templates, no strings. Each HTML tag is represented by a Python function, and attributes, styles, or child elements are defined using a clean, chainable syntax.
from htealeaf.elements import html, head, body, h1, div, button
page = html(
head(),
body(
h1("Welcome to HTeaLeaf!"),
div("This content is rendered from Python."),
button("Click me!")
)
)Each HTML tag in HTeaLeaf is represented by a Python function They all share the same pattern:
div(children...) # create a <div>
div().attr(name=value) # add an attribute
div().classes("a b") # add CSS classes (one space-separated string)
div().style(color="red") # inline style
div().id("main") # set the id attributeAttributes are added using .attr() it accepts any standard HTML attribute or event handler:
button("Save").attr(id="saveBtn", onclick="alert('Saved!')")
form().action("/submit").method("POST")
textInput().attr(name="email", placeholder="Enter your email")HTeaLeaf makes it easy to assign classes or inline styles. .classes() takes a single space-separated string; .style() converts snake_case keyword names to CSS kebab-case:
div("Hello").classes("card highlight")
h1("Title").style(color="green", text_align="center")For layout, div has two flexbox shortcuts:
div(a, b, c).row() # display: flex; flex-direction: row
div(a, b, c).column() # display: flex; flex-direction: columnBeyond inline styles, the style() element accepts a nested dict and compiles it to CSS. Nested dicts become descendant selectors, and a leading & concatenates with the parent selector (like SCSS):
from htealeaf.elements import style
style({
"body": {"background_color": "teal"},
".card": {
"padding": "10px",
"&:hover": {"box_shadow": "0 0 4px gray"}, # .card:hover
"h2": {"margin": "0"}, # .card h2
},
})style(href="...") links an external stylesheet instead.
Since HTeaLeaf integrates with its reactivity layer, event attributes like onclick can be tied to Store actions or custom JavaScript code (JSCode and JS from Python).
from htealeaf import Store
from htealeaf.elements import div, h1, button
store = Store({"counter": 0})
div(
button("-").attr(onclick=store.js.update("counter", store.read("counter") - 1)),
h1(store.read("counter")),
button("+").attr(onclick=store.js.update("counter", store.read("counter") + 1)),
)Here, the buttons directly update a reactive Store; after the mutation the client re-fetches the route and reconciles the DOM, so the counter re-renders automatically in the browser. No manual JS required.
You can insert any Python value or expression as a child:
user = "Alb"
div(f"Hello {user}!") # produces <div>Hello Alb!</div>Lists of elements are supported:
div(
[h2(f"Item {i}") for i in range(5)]
)HTeaLeaf includes a few specialized helpers that map to common patterns:
| Function | Description |
|---|---|
| textInput() | creates a text <input>
|
| submit(label) | <input type="submit">label</input> |
| checkbox(checked=False) | <input type="checkbox"> |
| form(...).action(url).method(verb) |
<form> with chainable action/method |
| select(items) / option(value) |
<select> built from a list of strings |
| label(...) |
<label> tag |
| Table / thead / tr / th / td | table building blocks (import from htealeaf.elements.elements) |
| link() |
<link> tag for stylesheets |
| script(code) |
<script> tag (content is emitted verbatim) |
| header() | semantic layout tag |
| tl_if(condition, *childs) | wraps children in a div whose display is driven by the condition (accepts JSCode, so it can react to client state); import from htealeaf.elements.elements
|
Standard containers and headings — html, head, body, div, h1–h3, button — are importable from htealeaf.elements as well. Table/td/tr/th/thead and tl_if are not re-exported there yet, so they come from the inner htealeaf.elements.elements module. Tags without a helper class (e.g. p, footer) don't exist yet; compose with div or contribute the element upstream.
Every element also supports:
card = div().classes("card")
card.append(h2("Title")) # add a child at the end
card.prepend(link()) # add a child at the start
card.id("main-card") # set id and return self
card.get_child("h2") # find a direct child by tag nameYou can define reusable components as simple Python functions:
def Card(title, content):
return div(
h2(title),
div(content)
).classes("card").style(padding="10px")
@app.route("/")
def home():
return html(
body(
Card("Welcome", "This is a HTeaLeaf component."),
Card("Info", "Reusable and declarative!")
)
)Next: Store
🍃 HTeaLeaf
State
Client-side