/SKILL.md
Generate NitroUI code - a Python library for programmatic HTML generation using classes instead of templates
npx skillsauth add nitrosh/nitro-ui nitro-uiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A zero-dependency Python 3.8+ library for programmatic HTML generation. Build HTML with Python classes instead of string templates.
from nitro_ui import Div, H1, Paragraph
page = Div(
H1("Welcome"),
Paragraph("Built with NitroUI"),
cls="container"
)
print(page.render())
# <div class="container"><h1>Welcome</h1><p>Built with NitroUI</p></div>
from nitro_ui import Div, H1, Paragraph, UnorderedList, ListItem, Image
from nitro_ui import HTMLElement, Fragment, Component, Slot, Partial, from_html
from nitro_ui.styles import CSSStyle, StyleSheet, Theme
| PascalCase | HTML | Notes |
|------------|------|-------|
| HTML | <html> | Includes <!DOCTYPE html>. Default lang="en", dir="ltr" (overridable) |
| Head | <head> | |
| Body | <body> | |
| Title | <title> | |
| Meta | <meta> | Self-closing |
| Link | <link> | Self-closing |
| Script | <script> | |
| Style | <style> | |
| Base | <base> | Self-closing |
| Noscript | <noscript> | |
| IFrame | <iframe> | |
| Template | <template> | |
| Svg | <svg> | SVG camelCase attrs supported (see below) |
| Math | <math> | |
| PascalCase | HTML |
|------------|------|
| Div | <div> |
| Section | <section> |
| Article | <article> |
| Header | <header> |
| Footer | <footer> |
| Nav | <nav> |
| Main | <main> |
| Aside | <aside> |
| HorizontalRule | <hr> (self-closing) |
| Details | <details> |
| Summary | <summary> |
| Dialog | <dialog> |
| Address | <address> |
| Hgroup | <hgroup> |
| Search | <search> |
| Menu | <menu> |
| PascalCase | HTML | Notes |
|------------|------|-------|
| H1-H6 | <h1>-<h6> | |
| Paragraph | <p> | |
| Span | <span> | |
| Strong | <strong> | |
| Em | <em> | |
| Bold | <b> | |
| Italic | <i> | |
| Underline | <u> | |
| Strikethrough | <s> | |
| Small | <small> | |
| Mark | <mark> | |
| Del | <del> | |
| Ins | <ins> | |
| Subscript | <sub> | |
| Superscript | <sup> | |
| Code | <code> | |
| Pre | <pre> | |
| Kbd | <kbd> | |
| Samp | <samp> | |
| Var | <var> | |
| Blockquote | <blockquote> | |
| Quote | <q> | |
| Cite | <cite> | |
| Abbr | <abbr> | |
| Dfn | <dfn> | |
| Time | <time> | |
| Anchor | <a> | Preferred name for anchor links |
| Href | <a> | Backward-compatible alias for Anchor |
| Br | <br> | Self-closing |
| Wbr | <wbr> | Self-closing |
| Bdi | <bdi> | Bidirectional isolation |
| Bdo | <bdo> | Bidirectional override |
| Ruby | <ruby> | Ruby annotation |
| Rt | <rt> | Ruby text |
| Rp | <rp> | Ruby fallback parenthesis |
| Data | <data> | Machine-readable value |
| PascalCase | HTML |
|------------|------|
| UnorderedList | <ul> |
| OrderedList | <ol> |
| ListItem | <li> |
| DescriptionList | <dl> |
| DescriptionTerm | <dt> |
| DescriptionDetails | <dd> |
| PascalCase | HTML | Notes |
|------------|------|-------|
| Form | <form> | Also has Form.with_fields() classmethod |
| Input | <input> | Self-closing |
| Button | <button> | |
| Textarea | <textarea> | |
| Select | <select> | Also has Select.with_items() classmethod |
| Option | <option> | |
| Optgroup | <optgroup> | |
| Label | <label> | |
| Fieldset | <fieldset> | |
| Legend | <legend> | |
| Output | <output> | |
| Progress | <progress> | |
| Meter | <meter> | |
| Datalist | <datalist> | |
| PascalCase | HTML |
|------------|------|
| Table | <table> |
| TableHeader | <thead> |
| TableBody | <tbody> |
| TableFooter | <tfoot> |
| TableRow | <tr> |
| TableHeaderCell | <th> |
| TableDataCell | <td> |
| Caption | <caption> |
| Colgroup | <colgroup> |
| Col | <col> (self-closing) |
Table.from_json(path) and Table.from_csv(path) create tables from files (first row = headers in <thead>, rest in <tbody>).
| PascalCase | HTML |
|------------|------|
| Image | <img> (self-closing) |
| Video | <video> |
| Audio | <audio> |
| Source | <source> (self-closing) |
| Track | <track> (self-closing) |
| Picture | <picture> |
| Figure | <figure> |
| Figcaption | <figcaption> |
| Canvas | <canvas> |
| Embed | <embed> (self-closing) |
| Object | <object> |
| Param | <param> (self-closing) |
| Map | <map> |
| Area | <area> (self-closing) |
HTMLElement(
*children, # HTMLElement, str, or nested lists (auto-flattened)
tag: str, # Required HTML tag name
self_closing: bool = False,
**attributes # HTML attributes as keyword arguments
)
Special attribute mappings:
cls or class_name → renders as classclass_ → also maps to class_namefor_element or for_ → renders as fordata_* → data-* (other underscores become hyphens)view_box → viewBox, preserve_aspect_ratio → preserveAspectRatio, etc. (52 SVG attributes supported)Boolean attributes (disabled, checked, required, hidden, readonly, autofocus, autoplay, controls, loop, muted, multiple, open, selected, defer, async, novalidate, formnovalidate, reversed, allowfullscreen, inert, playsinline, nomodule, default, ismap, itemscope):
True → bare attribute (e.g. <input disabled>)False → attribute omitted entirelyNone → attribute omitted entirelydiv = Div(
H1("Title"),
"Some text",
id="main",
cls="container",
data_value="123"
)
# <div id="main" class="container" data-value="123"><h1>Title</h1>Some text</div>
# Boolean attributes
Input(type="checkbox", checked=True, disabled=False)
# <input type="checkbox" checked />
# SVG camelCase attributes
HTMLElement(tag="svg", view_box="0 0 100 100", preserve_aspect_ratio="xMidYMid")
# <svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"></svg>
| Property | Type | Mutable | Notes |
|----------|------|---------|-------|
| tag | str | Yes | HTML tag name |
| children | List[HTMLElement] | Yes | Use methods preferred |
| text | str | Yes | Text content |
| attributes | dict | Yes | Setting invalidates style cache |
| self_closing | bool | Yes | |
| Method | Returns | Description |
|--------|---------|-------------|
| append(*children) | self | Add children to end |
| prepend(*children) | self | Add children to start |
| clear() | self | Remove all children |
| pop(index=0) | HTMLElement | Remove and return child |
| remove_all(condition) | self | Remove matching children |
| replace_child(index, new) | None | Replace child at index (not chainable) |
| count_children() | int | Number of direct children |
| first() | HTMLElement\|None | First child |
| last() | HTMLElement\|None | Last child |
| filter(cond, recursive=False, max_depth=1000) | Iterator | Matching children/descendants |
| find_by_attribute(key, value, max_depth=1000) | HTMLElement\|None | First descendant with matching attr |
Children can be HTMLElement, str, or nested lists. None is silently ignored. Other types raise ValueError.
| Method | Returns | Description |
|--------|---------|-------------|
| add_attribute(key, value) | self | Set single attribute |
| add_attributes([(k,v),...]) | self | Set multiple attributes |
| remove_attribute(key) | self | Remove attribute |
| get_attribute(key) | str\|None | Get attribute value |
| has_attribute(key) | bool | Check existence |
| get_attributes(*keys) | dict | Get all (or specified) attributes. Returns a copy. |
| generate_id() | None | Add unique ID if none exists (not chainable) |
| Method | Returns | Description |
|--------|---------|-------------|
| add_style(key, value) | self | Set CSS property. Raises ValueError on dangerous values. |
| add_styles(dict) | self | Set multiple CSS properties |
| get_style(key) | str\|None | Get CSS property value |
| remove_style(key) | self | Remove CSS property |
CSS values are validated against injection patterns (javascript:, expression(), url(data:), etc.).
| Method | Returns | Description |
|--------|---------|-------------|
| render(pretty=False, max_depth=1000) | str | HTML string. Raises RecursionError if depth exceeded. |
| str(element) | str | Same as render() |
| Method | Returns | Description |
|--------|---------|-------------|
| to_dict(max_depth=1000) | dict | {tag, self_closing, attributes, text, children} |
| to_json(indent=None) | str | JSON string |
| HTMLElement.from_dict(data, max_depth=1000) | HTMLElement | Reconstruct from dict. Validates input types. Restores subclass types via tag registry. |
| HTMLElement.from_json(json_str) | HTMLElement | Reconstruct from JSON |
| from_html(html_str, fragment=False) | HTMLElement\|List\|None | Parse HTML string. Preserves SVG camelCase attrs. |
from_html is also available as a standalone function: from nitro_ui import from_html
When fragment=True, returns List[HTMLElement]. When False, returns single element or None.
from_dict() reconstructs Fragment instances correctly and uses a tag registry to preserve subclass types where possible.
| Method | Called When |
|--------|------------|
| on_load() | Element constructed |
| on_before_render() | Before render() |
| on_after_render() | After render() |
| on_unload() | Element garbage collected |
| Method | Returns | Description |
|--------|---------|-------------|
| clone() | HTMLElement | Deep copy |
| __enter__/__exit__ | self | Context manager support |
Renders children without a wrapper element.
from nitro_ui import Fragment, H1, Paragraph
frag = Fragment(H1("Title"), Paragraph("Content"))
print(frag.render())
# <h1>Title</h1><p>Content</p>
Use cases: conditional rendering without wrapper divs, returning multiple elements from functions, list composition.
Embed raw HTML for trusted content. Bypasses escaping.
from nitro_ui import Head, Meta, Title, Partial
# Inline raw HTML
Head(
Meta(charset="utf-8"),
Partial("""
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_ID"></script>
<script>gtag('config', 'GA_ID');</script>
"""),
Title("My Page")
)
# Or load from file (lazy-loaded at render time)
Partial(file="partials/analytics.html")
Warning: Only use with trusted content - bypasses XSS protections.
Build reusable components with declarative templates and named slots.
from nitro_ui import Component, Slot, H3, Paragraph, Div, Button
class Card(Component):
tag = "div"
class_name = "card"
def template(self, title: str):
return [
H3(title, cls="card-title"),
Slot() # children go here
]
# Usage
card = Card("My Title",
Paragraph("Content goes here"),
id="card-1",
cls="featured"
)
# <div class="card featured" id="card-1">
# <h3 class="card-title">My Title</h3>
# <p>Content goes here</p>
# </div>
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| tag | str | "div" | Root element tag |
| class_name | str | None | Default CSS class(es) |
Override template() to define the component structure. Parameters become props.
def template(self, title: str, level: str = "info"):
return [
Span(f"[{level.upper()}]"),
Span(title),
Slot()
]
Use Slot() for the default slot (receives *children), Slot("name") for named slots.
class Modal(Component):
tag = "div"
class_name = "modal"
def template(self, title: str):
return [
Div(H2(title), Slot("actions"), cls="header"),
Div(Slot(), cls="body"),
Div(Slot("footer", default=Button("Close")), cls="footer")
]
# Usage - named slots via kwargs
Modal("Confirm",
Paragraph("Are you sure?"), # default slot
actions=Button("X"), # named slot
footer=[Button("Cancel"), Button("OK")] # named slot (list)
)
Slot rules:
Slot() — default slot, receives positional *childrenSlot("name") — named slot, receives name= kwargSlot("name", default=element) — fallback content if slot not providedArguments are separated automatically:
template() params)class Alert(Component):
def template(self, message: str, level: str = "info"):
return [Slot("icon"), Span(message)]
Alert("Hello", # prop: message
level="warning", # prop (template param)
icon=Icon("warning"), # slot
id="alert-1", # HTML attr
role="alert" # HTML attr
)
User-provided cls merges with the Component's default class_name (appends):
class Card(Component):
class_name = "card"
Card("Title", cls="featured")
# → <div class="card featured">...</div>
Components inherit HTMLElement lifecycle hooks:
class MyComponent(Component):
def on_load(self): ... # After construction
def on_before_render(self): ... # Before render()
def on_after_render(self): ... # After render()
Represents CSS styles with pseudo-selectors and responsive breakpoints.
from nitro_ui.styles import CSSStyle
style = CSSStyle(
background_color="#007bff", # snake_case -> kebab-case
color="white",
padding="10px 20px",
border_radius="5px",
_hover=CSSStyle(background_color="#0056b3"), # pseudo-selector
_md=CSSStyle(padding="15px 30px") # breakpoint
)
Pseudo-selectors (prefix _): _hover, _active, _focus, _visited, _link, _first_child, _last_child, _nth_child, _before, _after
Breakpoints (prefix _): _xs (0px), _sm (640px), _md (768px), _lg (1024px), _xl (1280px), _2xl (1536px)
| Method | Returns | Description |
|--------|---------|-------------|
| to_inline() | str | CSS string for style="..." (base styles only) |
| merge(other) | CSSStyle | New merged style (other overrides) |
| has_pseudo_or_breakpoints() | bool | Has pseudo/responsive styles |
| is_complex(threshold=3) | bool | Has many properties |
| to_dict() / CSSStyle.from_dict(data) | | Serialization |
Manages CSS classes and generates <style> tag content.
from nitro_ui.styles import StyleSheet, CSSStyle, Theme
# Optional theme for CSS variables
stylesheet = StyleSheet(theme=Theme.modern())
# Register classes
btn = stylesheet.register("btn-primary", CSSStyle(
background_color="#007bff",
color="white",
_hover=CSSStyle(background_color="#0056b3")
))
# BEM naming
card = stylesheet.register_bem("card", style=CSSStyle(padding="20px"))
# "card"
card_header = stylesheet.register_bem("card", element="header",
style=CSSStyle(font_weight="bold"))
# "card__header"
card_featured = stylesheet.register_bem("card", modifier="featured",
style=CSSStyle(border="2px solid blue"))
# "card--featured"
# Use in elements
Button("Click", cls=btn)
Div(cls=f"{card} {card_featured}")
# Generate output
css = stylesheet.render() # CSS string
tag = stylesheet.to_style_tag() # <style>...</style>
| Method | Returns | Description |
|--------|---------|-------------|
| register(name, style) | str | Register style, returns class name |
| register_bem(block, element=, modifier=, style=) | str | BEM class name |
| get_style(name) | CSSStyle\|None | Get registered style |
| has_class(name) | bool | Check if registered |
| unregister(name) | bool | Remove class |
| clear() | None | Remove all classes |
| set_breakpoint(name, value) | None | Set breakpoint value |
| render(pretty=True) | str | CSS output |
| to_style_tag(pretty=True) | str | <style> tag |
| count_classes() | int | Number of classes |
| get_all_class_names() | List[str] | All class names |
| to_dict() / StyleSheet.from_dict(data, theme=) | | Serialization |
Preset design systems with CSS variables.
from nitro_ui.styles import Theme
theme = Theme.modern() # Blue primary, Inter font, modern components
theme = Theme.classic() # Traditional blue, Georgia serif
theme = Theme.minimal() # Black/white, system fonts
# Custom
theme = Theme(
name="Custom",
colors={"primary": "#007bff", "secondary": "#6c757d"},
typography={"font_family": "Inter, sans-serif"},
spacing={"sm": "8px", "md": "16px", "lg": "24px"},
components={"button": {"primary": CSSStyle(...)}}
)
| Method | Returns | Description |
|--------|---------|-------------|
| get_css_variables() | dict | {"--color-primary": "#007bff", ...} |
| get_component_style(component, variant="default") | CSSStyle\|None | Component style |
| to_dict() / Theme.from_dict(data) | | Serialization |
from nitro_ui import HTML, Head, Body, Title, Meta, Div, H1, Paragraph
page = HTML(
Head(
Title("My Page"),
Meta(charset="utf-8"),
Meta(name="viewport", content="width=device-width, initial-scale=1")
),
Body(
Div(
H1("Welcome"),
Paragraph("Hello, world!"),
cls="container"
)
)
)
html = page.render(pretty=True)
from nitro_ui import Nav, UnorderedList, ListItem, Anchor
navbar = Nav(
UnorderedList(
ListItem(Anchor("Home", href="/")),
ListItem(Anchor("About", href="/about")),
ListItem(Anchor("Contact", href="/contact")),
),
cls="navbar"
)
from nitro_ui import Form, Label, Input, Button
login_form = Form(
Label("Email:", for_element="email"),
Input(type="email", id="email", name="email", required=True),
Label("Password:", for_element="password"),
Input(type="password", id="password", name="password", required=True),
Button("Log In", type="submit"),
action="/login",
method="post"
)
from nitro_ui import Table, TableHeader, TableBody, TableRow, TableHeaderCell, TableDataCell
data = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
t = Table(
TableHeader(TableRow(TableHeaderCell("Name"), TableHeaderCell("Age"))),
TableBody(*[
TableRow(TableDataCell(row["name"]), TableDataCell(str(row["age"])))
for row in data
])
)
from nitro_ui import UnorderedList, ListItem
items = ["Apple", "Banana", "Orange"]
list_element = UnorderedList(*[ListItem(item) for item in items])
container = (Div()
.add_attribute("id", "hero")
.add_styles({"background": "#f0f0f0", "padding": "2rem"})
.append(H1("Welcome"))
.append(Paragraph("Get started today.")))
from nitro_ui import Component, Slot, Div, H2, Paragraph
class Card(Component):
tag = "div"
class_name = "card"
def template(self, title: str):
return [
H2(title, cls="card-title"),
Div(Slot(), cls="card-body")
]
card = Card("My Card", Paragraph("Card content here"), id="card-1")
from nitro_ui import HTMLElement, H2, Paragraph
class Card(HTMLElement):
def __init__(self, title, content, **kwargs):
super().__init__(tag="div", cls="card", **kwargs)
self.append(
H2(title, cls="card-title"),
Paragraph(content, cls="card-body")
)
card = Card("My Card", "Card content here", id="card-1")
from nitro_ui import Svg, HTMLElement
# Using the Svg tag class with snake_case kwargs
icon = Svg(
HTMLElement(
HTMLElement(tag="circle", cx="12", cy="12", r="10"),
tag="g",
),
view_box="0 0 24 24",
preserve_aspect_ratio="xMidYMid meet",
cls="icon"
)
# Parsing SVG HTML preserves camelCase attributes
from nitro_ui import from_html
svg = from_html('<svg viewBox="0 0 100 100"><circle r="50"/></svg>')
print(svg.render())
# <svg viewBox="0 0 100 100"><circle r="50" /></svg>
from nitro_ui import from_html, Paragraph
element = from_html('<div class="old"><h1>Title</h1></div>')
element.add_attribute("class", "new")
element.add_style("padding", "20px")
element.append(Paragraph("New content"))
html = element.render()
from nitro_ui import Div, H1, HTMLElement
page = Div(H1("Title"), id="page")
json_str = page.to_json(indent=2)
loaded = HTMLElement.from_json(json_str)
html = loaded.render()
from nitro_ui import HTML, Head, Body, Button, Style
from nitro_ui.styles import CSSStyle, StyleSheet, Theme
theme = Theme.modern()
stylesheet = StyleSheet(theme=theme)
btn = stylesheet.register("btn", CSSStyle(
background_color="var(--color-primary)",
color="white",
padding="10px 20px",
_hover=CSSStyle(background_color="var(--color-primary-dark)")
))
page = HTML(
Head(Style(stylesheet.render())),
Body(Button("Click Me", cls=btn))
)
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from nitro_ui import HTML, Head, Body, Title, H1
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
async def home():
return HTML(
Head(Title("FastAPI + NitroUI")),
Body(H1("Hello!"))
).render()
from flask import Flask
from nitro_ui import HTML, Head, Body, Title, H1
app = Flask(__name__)
@app.route("/")
def home():
return HTML(
Head(Title("Flask + NitroUI")),
Body(H1("Hello!"))
).render()
from django.http import HttpResponse
from nitro_ui import HTML, Head, Body, Title, H1
def home(request):
page = HTML(
Head(Title("Django + NitroUI")),
Body(H1("Hello!"))
)
return HttpResponse(page.render())
Generate HTML5 form fields with validation attributes using the Field class.
from nitro_ui import Form, Button, Field
form = Form(
Field.email("email", label="Email", required=True, placeholder="[email protected]"),
Field.password("password", label="Password", min_length=8),
Field.checkbox("remember", label="Remember me"),
Button("Log In", type="submit"),
action="/login",
method="post"
)
All Field.* methods accept an optional id parameter (defaults to name when omitted) and forward any additional keyword arguments (including hx_*) to the underlying input via **attrs.
Field.text(name, label=None, required=False, min_length=None, max_length=None, pattern=None, placeholder=None, value=None, id=None, wrapper=None, **attrs)
Field.email(name, label=None, required=False, placeholder=None, value=None, wrapper=None, **attrs)
Field.password(name, label=None, required=False, min_length=None, max_length=None, placeholder=None, wrapper=None, **attrs)
Field.url(name, label=None, required=False, placeholder=None, value=None, wrapper=None, **attrs)
Field.tel(name, label=None, required=False, pattern=None, placeholder=None, value=None, wrapper=None, **attrs)
Field.search(name, label=None, required=False, placeholder=None, value=None, wrapper=None, **attrs)
Field.textarea(name, label=None, required=False, rows=None, cols=None, min_length=None, max_length=None, placeholder=None, value=None, wrapper=None, **attrs)
Field.number(name, label=None, required=False, min=None, max=None, step=None, value=None, wrapper=None, **attrs)
Field.range(name, label=None, min=0, max=100, step=None, value=None, wrapper=None, **attrs)
Field.date(name, label=None, required=False, min=None, max=None, value=None, wrapper=None, **attrs)
Field.time(name, label=None, required=False, min=None, max=None, value=None, wrapper=None, **attrs)
Field.datetime_local(name, label=None, required=False, min=None, max=None, value=None, wrapper=None, **attrs)
# Select with different option formats
Field.select("country", ["USA", "Canada", "Mexico"]) # strings
Field.select("status", [("active", "Active"), ("inactive", "Inactive")]) # tuples
Field.select("priority", [{"value": "1", "label": "Low", "disabled": True}]) # dicts
# Pre-selected value
Field.select("country", ["USA", "Canada"], value="Canada", label="Country")
# Checkbox (label wraps input, accepts checked=True and value="on")
Field.checkbox("terms", label="I agree to the Terms", required=True, checked=False)
# Radio buttons (wrapped in fieldset)
Field.radio("plan", [("free", "Free"), ("pro", "Pro")], label="Select Plan", value="free")
Field.file(name, label=None, required=False, accept=None, multiple=False, wrapper=None, **attrs)
Field.hidden(name, value, **attrs)
Field.color(name, label=None, value=None, wrapper=None, **attrs)
# No label - just input
Field.text("username")
# With label
Field.text("username", label="Username")
# → <label for="username">Username</label><input ...>
# With wrapper div
Field.text("username", label="Username", wrapper="form-field")
# → <div class="form-field"><label>...</label><input ...></div>
# Wrapper with attributes
Field.text("username", label="Username", wrapper={"cls": "form-group", "id": "field-1"})
Field.text("search",
placeholder="Search...",
hx_get="/search",
hx_trigger="keyup changed delay:300ms",
hx_target="#results"
)
NitroUI works seamlessly with HTMX. Use hx_* kwargs — underscores convert to hyphens automatically.
from nitro_ui import Button, Div, Input, Script
# Include HTMX
Script(src="https://unpkg.com/[email protected]")
# hx_get → hx-get, hx_target → hx-target, etc.
Button("Load More", hx_get="/items", hx_target="#list", hx_swap="beforeend")
# Click to load
Button("Load", hx_get="/content", hx_target="#result")
# Delete with confirmation
Button("Delete", hx_delete="/items/1", hx_confirm="Are you sure?", hx_swap="outerHTML")
# Live search
Input(
type="text",
name="q",
hx_get="/search",
hx_trigger="keyup changed delay:300ms",
hx_target="#results"
)
# Form submission
Form(
Input(type="text", name="email"),
Button("Subscribe"),
hx_post="/subscribe",
hx_swap="outerHTML"
)
# Infinite scroll
Div(
hx_get="/items?page=2",
hx_trigger="revealed",
hx_swap="afterend"
)
# Polling
Div(id="notifications", hx_get="/notifications", hx_trigger="every 30s")
| Python kwarg | HTML attribute | Description |
|--------------|----------------|-------------|
| hx_get | hx-get | GET request |
| hx_post | hx-post | POST request |
| hx_put | hx-put | PUT request |
| hx_patch | hx-patch | PATCH request |
| hx_delete | hx-delete | DELETE request |
| hx_target | hx-target | Target element selector |
| hx_swap | hx-swap | How to swap content |
| hx_trigger | hx-trigger | Event that triggers request |
| hx_confirm | hx-confirm | Confirmation dialog |
| hx_indicator | hx-indicator | Loading indicator |
| hx_push_url | hx-push-url | Push URL to history |
| hx_select | hx-select | Select content from response |
| hx_select_oob | hx-select-oob | Out-of-band select |
| hx_swap_oob | hx-swap-oob | Out-of-band swap |
| hx_vals | hx-vals | Additional values (JSON) |
| hx_boost | hx-boost | Boost all links/forms |
| hx_include | hx-include | Include additional inputs |
| hx_params | hx-params | Filter parameters |
| hx_preserve | hx-preserve | Preserve element |
| hx_ext | hx-ext | Extensions |
# Server-Sent Events
Div(hx_ext="sse", sse_connect="/events", sse_swap="message")
# WebSockets
Div(hx_ext="ws", ws_connect="/ws")
# JSON encoding
Form(hx_ext="json-enc", hx_post="/api/submit")
add_style/add_styles/CSSStyle.to_inline() reject javascript:, expression(), url(data:), CSS hex escapes, etc. (raises ValueError). Uses a single shared validation implementation.^[a-zA-Z][a-zA-Z0-9-]*$. Invalid names raise ValueError.disabled=False correctly omits the attribute (browsers treat any present attribute as truthy).None are omitted from output (not rendered as literal "None").Partial for trusted raw HTML)render(), filter(), find_by_attribute(), to_dict(), from_dict() all have max_depth parameter (default 1000) to prevent stack overflow from circular referencesfrom_dict() validates all field types. StyleSheet.from_dict() validates class names.HTMLElement and str accepted as children. None silently ignored, other types raise ValueError.<img>, <input>) emits a UserWarning.nitro-ui, import is nitro_uiAnchor is the preferred name for <a> tags. Href is a backward-compatible alias. Both work identically.<link> tags: Link renders <link> (stylesheet/meta). Use Anchor (or Href) for <a> links.children property returns a copy: Mutating the returned list does not affect the element. Use append()/prepend() to modify.get_attributes() returns a copy: Mutating the returned dict does not affect the element. Use add_attribute() to modify.replace_child() and generate_id() return None: Not chainable, unlike other methods.from_dict() expects normalized keys: Designed for round-tripping with to_dict(). Attribute keys should already be in their final form (e.g. data-value, not data_value).view_box not viewBox when constructing via kwargs. The camelCase form is used for storage and rendering. get_attribute("viewBox") to access.disabled=True renders as bare disabled, disabled=False omits it entirely. disabled="False" still renders (as a string value).When generating NitroUI code:
from nitro_ui import Div, H1, Paragraphcls for CSS classes (e.g., Div(cls="container"))for_element not for (Python keyword)Anchor (or Href) for <a> tags, Link for <link> tags.render() to get HTML stringpretty=True for readable output during developmentself for chaining (except replace_child, generate_id)Fragment when you need multiple elements without a wrapperPartial for raw HTML (analytics, embeds) - bypasses escapingComponent + Slot for reusable components with declarative templatesField.xyz() for form fields with HTML5 validation attributesTrue/False, not strings, for disabled, checked, required, etc.view_box, preserve_aspect_ratio) - auto-converted to camelCasedevelopment
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
development
Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.
development
Run, watch, debug, and extend OpenClaw QA testing with qa-lab and qa-channel. Use when Codex needs to execute the repo-backed QA suite, inspect live QA artifacts, debug failing scenarios, add new QA scenarios, or explain the OpenClaw QA workflow. Prefer the live OpenAI lane with regular openai/gpt-5.4 in fast mode; do not use gpt-5.4-pro or gpt-5.4-mini unless the user explicitly overrides that policy.
development
End-to-end Parallels smoke, upgrade, and rerun workflow for OpenClaw across macOS, Windows, and Linux guests. Use when Codex needs to run, rerun, debug, or interpret VM-based install, onboarding, gateway smoke tests, latest-release-to-main upgrade checks, fresh snapshot retests, or optional Discord roundtrip verification under Parallels.