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_ (or cls) is translated to the HTML class attribute, 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.