nitro-ui
npx skills add https://github.com/nitrosh/nitro-ui --skill nitro-ui
Agent 安装分布
Skill 文档
NitroUI Skill Guide
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>
Imports
Standard Import
from nitro_ui import Div, H1, Paragraph, UnorderedList, ListItem, Image
Core Classes
from nitro_ui import HTMLElement, Fragment, Component, Slot, Partial, from_html
Styling System
from nitro_ui.styles import CSSStyle, StyleSheet, Theme
Lowercase HTML-like aliases (div, p, a, etc.) are also available via from nitro_ui.html import *.
All Tags
Document Structure
| PascalCase | Lowercase | HTML | Notes |
|---|---|---|---|
HTML |
html |
<html> |
Includes <!DOCTYPE html> |
Head |
head |
<head> |
|
Body |
body |
<body> |
|
Title |
title |
<title> |
|
Meta |
meta |
<meta> |
Self-closing |
HtmlLink |
link |
<link> |
Self-closing. HtmlLink avoids confusion with Href |
Script |
script |
<script> |
|
Style |
style |
<style> |
|
Base |
base |
<base> |
Self-closing |
Noscript |
noscript |
<noscript> |
|
IFrame |
iframe |
<iframe> |
Layout
| PascalCase | Lowercase | HTML |
|---|---|---|
Div |
div |
<div> |
Section |
section |
<section> |
Article |
article |
<article> |
Header |
header |
<header> |
Footer |
footer |
<footer> |
Nav |
nav |
<nav> |
Main |
main |
<main> |
Aside |
aside |
<aside> |
HorizontalRule |
hr |
<hr> (self-closing) |
Details |
details |
<details> |
Summary |
summary |
<summary> |
Dialog |
dialog |
<dialog> |
Text
| PascalCase | Lowercase | HTML |
|---|---|---|
H1–H6 |
h1–h6 |
<h1>–<h6> |
Paragraph |
p |
<p> |
Span |
span |
<span> |
Strong |
strong |
<strong> |
Em |
em |
<em> |
Bold |
b |
<b> |
Italic |
i |
<i> |
Underline |
u |
<u> |
Strikethrough |
s |
<s> |
Small |
small |
<small> |
Mark |
mark |
<mark> |
Del |
del_ |
<del> |
Ins |
ins |
<ins> |
Subscript |
sub |
<sub> |
Superscript |
sup |
<sup> |
Code |
code |
<code> |
Pre |
pre |
<pre> |
Kbd |
kbd |
<kbd> |
Samp |
samp |
<samp> |
Var |
var |
<var> |
Blockquote |
blockquote |
<blockquote> |
Quote |
q |
<q> |
Cite |
cite |
<cite> |
Abbr |
abbr |
<abbr> |
Dfn |
dfn |
<dfn> |
Time |
time |
<time> |
Href |
a |
<a> |
Br |
br |
<br> (self-closing) |
Wbr |
wbr |
<wbr> (self-closing) |
Lists
| PascalCase | Lowercase | HTML |
|---|---|---|
UnorderedList |
ul |
<ul> |
OrderedList |
ol |
<ol> |
ListItem |
li |
<li> |
Datalist |
datalist |
<datalist> |
DescriptionList |
dl |
<dl> |
DescriptionTerm |
dt |
<dt> |
DescriptionDetails |
dd |
<dd> |
Forms
| PascalCase | Lowercase | HTML |
|---|---|---|
Form |
form |
<form> |
Input |
input_ |
<input> (self-closing) |
Button |
button |
<button> |
Textarea |
textarea |
<textarea> |
Select |
select |
<select> |
Option |
option |
<option> |
Optgroup |
optgroup |
<optgroup> |
Label |
label |
<label> |
Fieldset |
fieldset |
<fieldset> |
Legend |
legend |
<legend> |
Output |
output |
<output> |
Progress |
progress |
<progress> |
Meter |
meter |
<meter> |
Tables
| PascalCase | Lowercase | HTML |
|---|---|---|
Table |
table |
<table> |
TableHeader |
thead |
<thead> |
TableBody |
tbody |
<tbody> |
TableFooter |
tfoot |
<tfoot> |
TableRow |
tr |
<tr> |
TableHeaderCell |
th |
<th> |
TableDataCell |
td |
<td> |
Caption |
caption |
<caption> |
Colgroup |
colgroup |
<colgroup> |
Col |
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>).
Media
| PascalCase | Lowercase | HTML |
|---|---|---|
Image |
img |
<img> (self-closing) |
Video |
video |
<video> |
Audio |
audio |
<audio> |
Source |
source |
<source> (self-closing) |
Track |
track |
<track> (self-closing) |
Picture |
picture |
<picture> |
Figure |
figure |
<figure> |
Figcaption |
figcaption |
<figcaption> |
Canvas |
canvas |
<canvas> |
Embed |
embed |
<embed> (self-closing) |
Object |
object_ |
<object> |
Param |
param |
<param> (self-closing) |
Map |
map_ |
<map> |
Area |
area |
<area> (self-closing) |
HTMLElement Constructor
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:
clsorclass_nameâ renders asclassclass_â also maps toclass_namefor_elementorfor_â renders asfordata_*âdata-*(other underscores become hyphens)
div = 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>
Properties
| 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 |
All Methods
Children
| 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.
Attributes
| 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) |
Inline Styles
| 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.).
Rendering
| Method | Returns | Description |
|---|---|---|
render(pretty=False, max_depth=1000) |
str |
HTML string. Raises RecursionError if depth exceeded. |
str(element) |
str |
Same as render() |
Serialization
| Method | Returns | Description |
|---|---|---|
to_dict() |
dict |
{tag, self_closing, attributes, text, children} |
to_json(indent=None) |
str |
JSON string |
HTMLElement.from_dict(data) |
HTMLElement |
Reconstruct from dict |
HTMLElement.from_json(json_str) |
HTMLElement |
Reconstruct from JSON |
from_html(html_str, fragment=False) |
HTMLElement|List|None |
Parse HTML string |
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.
Event Callbacks (Override in Subclasses)
| Method | Called When |
|---|---|
on_load() |
Element constructed |
on_before_render() |
Before render() |
on_after_render() |
After render() |
on_unload() |
Element garbage collected |
Utility
| Method | Returns | Description |
|---|---|---|
clone() |
HTMLElement |
Deep copy |
__enter__/__exit__ |
self |
Context manager support |
Fragment (No Wrapper Tag)
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.
Partial (Raw HTML)
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.
Component (Reusable Components)
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>
Class Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
tag |
str |
"div" |
Root element tag |
class_name |
str |
None |
Default CSS class(es) |
Template Method
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()
]
Slots
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, receivesname=kwargSlot("name", default=element)â fallback content if slot not provided- Named slot kwargs accept single element or list
- Empty slots render nothing
Props vs Attributes
Arguments are separated automatically:
- HTMLElement args â default slot children
- Non-HTMLElement positional args â props (matched to
template()params) - Kwargs matching template params â props
- Kwargs matching slot names â slot content
- Other kwargs â HTML attributes on root element
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
)
Class Name Merging
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>
Lifecycle Hooks
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()
Styling System
CSSStyle
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 |
StyleSheet
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 |
Theme
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 |
Common Patterns
Complete Page
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)
Navigation
from nitro_ui import Nav, UnorderedList, ListItem, Href
navbar = Nav(
UnorderedList(
ListItem(Href("Home", href="/")),
ListItem(Href("About", href="/about")),
ListItem(Href("Contact", href="/contact")),
),
cls="navbar"
)
Form
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"
)
Table from Data
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
])
)
Dynamic List
from nitro_ui import UnorderedList, ListItem
items = ["Apple", "Banana", "Orange"]
list_element = UnorderedList(*[ListItem(item) for item in items])
Method Chaining
container = (Div()
.add_attribute("id", "hero")
.add_styles({"background": "#f0f0f0", "padding": "2rem"})
.append(H1("Welcome"))
.append(Paragraph("Get started today.")))
Custom Component (Recommended)
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")
Custom Component (Alternative – Direct Subclass)
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")
Parsing and Modifying HTML
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()
Serialization Round-Trip
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()
Page with StyleSheet
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))
)
Framework Integration
FastAPI
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()
Flask
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()
Django
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())
Form Builder
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="you@example.com"),
Field.password("password", label="Password", min_length=8),
Field.checkbox("remember", label="Remember me"),
Button("Log In", type="submit"),
action="/login",
method="post"
)
Text Fields
Field.text(name, label=None, required=False, min_length=None, max_length=None, pattern=None, placeholder=None, value=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)
Numeric & Date Fields
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)
Selection Fields
# 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)
Field.checkbox("terms", label="I agree to the Terms", required=True)
# Radio buttons (wrapped in fieldset)
Field.radio("plan", [("free", "Free"), ("pro", "Pro")], label="Select Plan", value="free")
Other Fields
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)
Labels and Wrappers
# 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"})
With HTMX
Field.text("search",
placeholder="Search...",
hx_get="/search",
hx_trigger="keyup changed delay:300ms",
hx_target="#results"
)
HTMX Integration
NitroUI works seamlessly with HTMX. Use hx_* kwargs â underscores convert to hyphens automatically.
Basic Usage
from nitro_ui import Button, Div, Input, Script
# Include HTMX
Script(src="https://unpkg.com/htmx.org@2.0.4")
# hx_get â hx-get, hx_target â hx-target, etc.
Button("Load More", hx_get="/items", hx_target="#list", hx_swap="beforeend")
Common Patterns
# 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")
All HTMX Attributes
| 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 |
HTMX 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")
Security
- Automatic HTML escaping: All text content and attribute values are escaped
- CSS value validation:
add_style/add_stylesrejectjavascript:,expression(),url(data:), etc. (raisesValueError) - CSS class name validation: StyleSheet rejects class names containing injection patterns
- No raw HTML injection: Cannot inject unescaped HTML (use
Partialfor trusted raw HTML) - Recursion protection:
render(),filter(),find_by_attribute()havemax_depthparameter (default 1000) to prevent stack overflow from circular references - Child type validation: Only
HTMLElementandstraccepted as children.Nonesilently ignored, other types raiseValueError.
Gotchas
- Package name mismatch: PyPI is
nitro-ui, import isnitro_ui - HtmlLink vs Href:
Hrefis for<a>links.HtmlLinkis for<link>tags. get_attributes()returns a copy: Mutating the returned dict does not affect the element. Useadd_attribute()to modify.replace_child()andgenerate_id()returnNone: Not chainable, unlike other methods.from_dict()expects normalized keys: Designed for round-tripping withto_dict(). Attribute keys should already be in their final form (e.g.data-value, notdata_value).
Quick Checklist
When generating NitroUI code:
- Use PascalCase imports:
from nitro_ui import Div, H1, Paragraph - Use
clsfor CSS classes (e.g.,Div(cls="container")) - Use
for_elementnotfor(Python keyword) - Children go as positional args, attributes as keyword args
- Call
.render()to get HTML string - Use
pretty=Truefor readable output during development - All manipulation methods return
selffor chaining (exceptreplace_child,generate_id) - Use
Fragmentwhen you need multiple elements without a wrapper - Use
Partialfor raw HTML (analytics, embeds) – bypasses escaping - Use
Component+Slotfor reusable components with declarative templates - Use
Field.xyz()for form fields with HTML5 validation attributes