Components
Components are plain Python functions that build HTML with the
koyoapp.html tag factory. No JSX, no template language.
Tag factories#
Every HTML tag has a factory with the same name in
koyoapp.html. Call it with element children
positionally and attributes as trailing keywords:
from koyoapp.html import div, h1, p
card = div(h1("Title"), p("Body text"), class_="rounded-lg border p-4")
Or build children after the call with subscripting. You must call the factory first, subscripting happens on the result:
card = div(class_="rounded-lg border p-4")[
h1("Title"),
p("Body text"),
]
Both produce a nested structure that render_html()
serializes to a string. Passing a list as a child flattens it in
place.
Attributes#
-
class_(orcls) is translated to the HTMLclassattribute, so the name does not shadow Python keywords. - Other attributes pass through with their Python names.
-
Boolean-style flags from htmx, such as
hx_trigger, pass through and become their hypenated attribute (hx-trigger).
div(class_="flex items-center gap-2", id="greeting", hx_post="/greet")
Children and escaping#
String children are HTML-escaped automatically. Tags can be nested freely as positional arguments or inside subscript brackets:
nav(ul(li("first item"), li("second item")))
# or: nav[
# ul[
# li("first item"),
# li("second item"),
# ],
# ]
Passing a raw string that is not safe to escape is where
raw() comes in:
from koyoapp.html import div, raw
div(raw("<strong>already HTML</strong>"))
Rendering to a string#
render_html(element) turns a tag tree into an HTML
string. Pages return that string, or return the element directly and
let Koyo serialize it:
from koyoapp.html import div, render_html
def page():
return render_html(div("Hello"))
# or simply: return div("Hello")
Reusable components#
Put reusable pieces in a components/ folder at the
project root, then import them by absolute module name:
# components/site.py
from koyoapp.html import div, footer, span
def site_footer():
return footer(div(span("Built with Koyo"), cls="max-w-6xl mx-auto px-4"), cls="border-t")
# app/layout.py
from components.site import site_footer
Koyo places the project root on the import path during development,
so components.site resolves. The scaffold uses this for
the landing page hero (components/landing.py), the
session counter (components/counter.py), and the site
footer (components/site.py).
Component conventions#
- Components are functions returning a tag tree.
- Optional arguments have explicit defaults, especially
request=None. - Keep presentational state out of components; let use_state own it.
- Use
raw()sparingly and never on user input.