Skip to content

HTML helpers

wybthon.html

html

Pythonic HTML element helpers that wrap h().

These helpers let you author markup that reads more like Python than hyperscript. Instead of writing:

h("div", {"class": "card", "on_click": handler}, h("p", {}, "Hello"))

you can write:

div(p("Hello"), class_="card", on_click=handler)

Children are positional arguments and props are keyword arguments.

Prop name mapping (Python keyword to HTML attribute):

  • class_ becomes class and html_for becomes for (reserved words).
  • Underscores become hyphens: aria_label, data_testid, tabindex stays as is. Event handlers keep the on_ prefix (on_click).
  • True sets a boolean attribute, False or None omits it.

Children may also be t-strings: p(t"Count: {count}") updates as one reactive text node.

Each helper returns a VNode. Element names that collide with Python builtins or keywords are exposed with a trailing underscore: main_, input_, del_, and object_. SVG elements live in wybthon.svg.

See Also
  • h: the underlying hyperscript constructor.
  • Fragment: group children with no DOM parent.

Functions:

Name Description
Fragment

Group children without adding a wrapper element.

element

Create a helper fn(*children, **props) -> VNode for any tag name.

Fragment

Fragment(*args: Any) -> VNode

Group children without adding a wrapper element.

Fragments mount their children directly into the parent between two empty comment markers, so they never pollute selectors like :first-child or affect layout.

Parameters:

Name Type Description Default
*args Any

Children. Lists and tuples are flattened.

()

Returns:

Type Description
VNode

A _fragment VNode.

Example
Fragment(h1("Title"), p("Body text"))

element

element(tag: str) -> Callable[..., VNode]

Create a helper fn(*children, **props) -> VNode for any tag name.

Use it for custom elements or tags without a built-in helper:

my_widget = element("my-widget")
my_widget("content", size="large")

What's in this module

One helper per HTML element, each building a VNode with children as positional arguments and props as keyword arguments, plus element() for tags without a built-in helper. Every helper is also re-exported from wybthon. Children can also be passed with item syntax, div(class_="card")[h2("Title"), p("Body")], which keeps long prop lists ahead of the content.

Group Helpers
Layout div, span, section, article, aside, header, footer, main_, nav, address, hgroup, search
Headings h1 to h6
Text p, a, strong, em, b, i, u, s, small, code, kbd, samp, var, pre, br, wbr, hr, blockquote, q, cite, abbr, dfn, mark, time, data, sub, sup, del_, ins, bdi, bdo, ruby, rt, rp
Lists ul, ol, li, dl, dt, dd, menu
Tables table, thead, tbody, tfoot, tr, th, td, caption, colgroup, col
Forms form, input_, textarea, select, option, optgroup, button, label, fieldset, legend, output, progress, meter, datalist
Media and embedded content img, video, audio, source, canvas, picture, track, iframe, embed, object_, map_, area
Interactive details, summary, dialog
Semantic and templating figure, figcaption, template, slot
Factory element(tag) returns a helper for any tag name (custom elements included).

Naming rules

The helpers store props exactly as written (div(class_="x").props == {"class_": "x"}); the prop applier maps the names when it writes attributes:

  • class_ becomes class, and html_for (or for_) becomes for.
  • Other underscores become hyphens: aria_label, data_testid, stroke_width.
  • input_, main_, del_, object_, and map_ carry a trailing underscore because their names collide with Python builtins or keywords.
  • Event handlers keep the on_ prefix: on_click, on_input.

Prop values

Prop value or name What the applier does
True Sets the attribute. Known boolean attributes (disabled, checked, hidden, required, readonly, selected, multiple, open, autofocus, ...) get ""; anything else gets "true".
False or None Removes the attribute.
value, checked, selected_values, inner_html / innerHTML Set as DOM properties, not attributes. value and checked are always re-asserted on patch so controlled inputs win over user edits.
class_ as a str, list, or dict Lists join truthy entries; dicts include keys whose values are truthy.
style as a dict or str Dict keys may be snake_case or camelCase and are converted to kebab-case; None or False removes a declaration; a string sets the style attribute.
dataset={...} Each key becomes a data-* attribute.
on_click, on_input, onClick, ... Registered with root-scoped event delegation; see events.
ref A Ref, a callback ref(el), or a list of either; assigned an Element on mount, Ref.current reset to None on unmount.
key, children Never written to the DOM.

Any prop value that's an accessor, a zero-arg function, or a template string with a reactive interpolation (class_=t"btn btn-{kind}"), or a class_ or style dict containing one, becomes a reactive binding: its own render effect re-applies just that prop when its reads change. A template string with no reactive interpolations is static. A binding that raises NotReadyError keeps the current DOM value; other exceptions route to the nearest Errored boundary.

from wybthon import Ref, a, button, create_signal, div, element, input_, label, main_, p

name, set_name = create_signal("")
active, set_active = create_signal(False)
field = Ref()
my_widget = element("my-widget")

view = main_(
    div(class_="form-row", data_testid="name-row")[
        label("Name", html_for="name"),
        input_(id="name", type="text", value=name, on_input=lambda e: set_name(e.target.value), ref=field),
        button(
            "Save",
            type="submit",
            class_={"btn": True, "btn-active": active},  # reactive dict entry
            aria_pressed=active,  # renders "true" or is removed
            disabled=lambda: name() == "",  # boolean attribute
            on_click=lambda: set_active(lambda v: not v),
        ),
    ],
    p(t"Hello, {name}! Read the ", a("docs", href="https://wybthon.com/"), "."),
    my_widget("custom element content", size="large"),
)

SVG

SVG elements live in wybthon.svg with the same calling convention. The reconciler infers the SVG namespace from the svg root, so an SVG subtree drops straight into an HTML tree. Names that clash with HTML helpers (a, title, text) carry their SVG meaning there.

See also