Usage
Overview¶
reactpy.html is how you write markup in ReactPy. It is an object whose attributes are tag constructors: html.div(...) builds a <div>, and there is no template language, no JSX, and no parser involved.
from reactpy import html
html.div({"class": "card"}, html.h2("Title"), html.p("Body"))
That call produces a plain dict:
{
"children": [
{"children": ["Title"], "tagName": "h2"},
{"children": ["Body"], "tagName": "p"},
],
"attributes": {"class": "card"},
"tagName": "div",
}
reactpy.h is the same object under a shorter name — h is html is True — so use whichever reads better in a given file. Importing both and mixing them in one module buys nothing.
Reference¶
html.<tag_name>(attributes, *children)¶
Every attribute of html is a tag constructor. The common tags are declared for autocomplete, but any name works — see Any name is a tag.
-
First argument: the attributes — but only when it is a plain
dict. AVdomDict, or any dict subclass, is treated as a child instead. This single rule is the most common source of confusion, and it is worth internalizing:html.div({"class": "card"}, html.p("Body")) # dict -> attributes html.div(html.p("Body")) # no dict -> no attributes html.div(None, html.p("Body")) # None -> an explicit "no attributes"Leading
Noneis the conventional way to say "no attributes" and keep the arguments in a predictable shape. It renders nothing.A
VdomDictas the first argument is a childVdomDictis adictsubclass, and the check is for the exact plaindicttype. So aVdomDictis never mistaken for attributes — even when you meant it that way:from reactpy.types import VdomDict html.div(VdomDict(tagName="span")) # -> a <span> *child* of the div -
Remaining arguments: children, of any type Common Props accepts — strings, numbers,
None, lists, dicts, components. Lists are flattened. -
Returns: a
VdomDict. Tag constructors are cached, sohtml.div is html.div— the same object every time. Nothing is rendered until a component returns it.
Caveats¶
- A plain
dictthat is meant as an element can be read as attributes.html.div({"children": ["x"]})produces a div with an attribute literally namedchildren, not a child — because the first argument was a plain dict. When in doubt, pass the element as the second argument afterNone. - Void tags reject children.
html.br("x")raisesTypeError: 'br' nodes cannot have children.See Common Props. - Tags do not validate their own attributes.
html.img({"scr": "typo.png"})is accepted and renders an image with no source. There is no prop-type check; a misspelled attribute name is a silent no-op.
html(*children) — fragments¶
Calling html itself is the fragment constructor: children with no wrapper element. See Common Props — a fragment accepts key and nothing else.
Usage¶
Any name is a tag¶
ReactPy does not keep a list of valid HTML tags. It converts the attribute name and sends it as the tag name, so you can reach tags the module has never declared:
| You write | You get |
|---|---|
html.div | <div> |
html.data_table | <data-table> |
html.my_widget | <my-widget> |
html.del_ | <del> |
html.class_ | <class> |
Two transformations: a trailing underscore is stripped (so you can name tags that are Python keywords, like del and class), and remaining underscores become dashes (so you can name custom elements and hyphenated tags that are not valid Python identifiers).
That makes custom elements first-class — no registration step, no wrapper component:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
1 | |
The <user-badge> element reaches the browser as written. Whether it does anything is up to whatever defines it — a web component, an external script, or nothing at all. A tag the browser does not know renders as an unknown element: its children show up, its attributes are set, and it is laid out inline by default.
Prefer a component over a mystery tag
A custom tag is only worth it when something on the client implements it. For grouping your own markup, write a @component — it takes part in rendering, can hold state, and is checkable by a type checker.
Tags versus components¶
Both are called the same way and both produce VDOM, and the difference is what happens next:
html.div(...) | my_component(...) | |
|---|---|---|
| Returns | A VdomDict, immediately | A Component, rendered later by the layout |
| Owns state | No | Yes — hooks belong to it |
| Re-rendered | Only when its parent returns a new value | When its state changes or its parent re-renders it |
| Defined with | Provided by html | @component over a function |
The practical rule: a tag is for HTML elements, a component is for anything with behaviour. A <div> that has to remember something is not a div any more — it is a component that returns a div. See Your First Component.
Nested and adjacent tags¶
Tags nest by call nesting, and read left-to-right, innermost first:
html.p(html.em("emphasized"), " and ", html.strong("bold"))
Because Python evaluates arguments eagerly, the inner call completes before the outer one runs. There is no indentation-scope or closing-tag syntax to get wrong, and no unbalanced-tag error — the structure either parses or it does not.
The one habit to build is the leading None. Without it, a first child that is a plain dict is consumed as attributes:
html.div({"tagName": "span"}) # attributes named "tagName" — not a child
html.div(None, {"tagName": "span"}) # a <span> child
A complete page reads like the HTML it produces:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
1 | |
Lists of tags¶
A list comprehension produces children directly; give each one a key:
html.ul(None, [html.li({"key": word}, word) for word in words])
See Rendering Lists for why the key matters, and Common Props for where it lives in the dictionary.
Classes and inline styles¶
class and className both work, and style takes a dict. Include units inside the strings — a bare number is applied by some executors and silently dropped by others:
html.p({"class": "muted"}, "...")
html.p({"style": {"font-size": "12px", "color": "#666"}}, "...")
Details, including the executor difference, are in Common Props.
SVG¶
SVG elements live under html.svg, because their tag names collide with HTML ones (<title>, <a>, <style>):
html.svg(
{"viewBox": "0 0 24 24", "width": "24"},
html.svg.circle({"cx": "12", "cy": "12", "r": "10"}),
)
html.svg is itself a tag constructor, and nested names follow the same rules — trailing underscore stripped, underscores to dashes. Camel-cased SVG names are used as written, so html.svg.clipPath produces <clipPath>.
Names are not case-normalized
viewBox works because the name is forwarded exactly. Write SVG attribute names the way the spec spells them, capitals included.
Raw HTML¶
Two different jobs get confused here. To put a raw HTML string into the page as-is, without ReactPy parsing it, use dangerouslySetInnerHTML:
html.div({"dangerouslySetInnerHTML": {"__html": "<b>bold</b>"}})
To hand ReactPy a block of HTML and have it parsed into VDOM — real elements you can then inspect and transform — use string_to_reactpy() instead, which is exported at the top of the package:
from reactpy import string_to_reactpy
string_to_reactpy("<b>bold</b>")
# {'children': ['bold'], 'tagName': 'b'}
See Convert Between VDOM and HTML for the parse direction in full, including the *transforms argument.
Never pass untrusted input to either one: dangerouslySetInnerHTML is inserted without sanitizing, and string_to_reactpy() parses whatever you give it. Note also that a dangerouslySetInnerHTML attribute suppresses children rather than raising — nothing checks the combination, the raw HTML simply wins and the children are dropped. See Common Props.
Inspecting what a tag produced¶
A tag call is a plain dict, so print it:
>>> from reactpy import html
>>> html.li({"key": "a"}, "Item")
{'children': ['Item'], 'attributes': {'key': 'a'}, 'tagName': 'li'}
Key order can differ, and empty keys are omitted — a tag with no attributes has no "attributes" entry at all. To see what a component renders, drive it through reactpy.core.layout.Layout rather than printing the component instance, which shows only its arguments.
Script tags¶
html.script is special-cased. It rejects event handlers:
html.script({"onLoad": handler})
ValueError: 'script' elements do not support event handlers
and it infers its own key from its content, falling back to src, so the script is not re-run on every render:
>>> html.script({"src": "/analytics.js"})
{'tagName': 'script', 'attributes': {'src': '/analytics.js', 'key': '/analytics.js'}}
Because the key drives re-execution, giving a script an explicit key is how you make it run again on purpose. As with dangerouslySetInnerHTML, keep untrusted data out of the body of a script.
Troubleshooting¶
The first argument became an attribute¶
If your element has an attribute named children or tagName and renders nothing, a plain dict child was read as the attributes dict. Pass None first.
TypeError: '<tag>' nodes cannot have children.¶
You passed content to a void tag. Remove the children.
TypeError: Fragments cannot have attributes besides 'key'¶
Give the fragment a real element, or move the attributes onto a child.
An attribute has no effect¶
The name is forwarded verbatim and never validated, so a typo is invisible. Print the dict and compare the key against what the browser expects. Only class/className and for/htmlFor have aliases; nothing else does.
A numeric style does nothing¶
Add the unit to the string. See Common Props.
At a glance¶
Tag names¶
| You write | You get | Rule |
|---|---|---|
html.div | <div> | as-is |
html.data_table | <data-table> | _ → - |
html.del_ | <del> | trailing _ stripped |
html.class_ | <class> | trailing _ stripped |
html.svg.clipPath | <clipPath> | as-is, under svg |
Arguments¶
| Position | Type | Meaning |
|---|---|---|
| first | plain dict | attributes, or pass None for none |
| first (otherwise) | any accepted child | a child |
| rest | any accepted child | children, flattened |
Accepted children¶
| Child | Renders as |
|---|---|
str | text, verbatim |
int / float | str(value) |
None | nothing |
True / False | the words True / False |
list / tuple | flattened children |
dict | an element (needs "tagName") |
@component instance | that component's output |
Related¶
- Common Props — the VDOM dictionary: attributes,
style,key, event handlers,dangerouslySetInnerHTML - Your First Component — components, and when to write one instead of a tag
- Rendering Lists — keys
- Responding to Events —
onClickand friends - ReactPy — serving a component tree to a browser