Building UIs
How to build user interfaces with HTML elements, widgets, and styles.
HTML Elements
The html module (typically imported as h) provides all standard HTML elements:
from trellis import html as h
h.Div() # <div>
h.Span() # <span>
h.P() # <p>
h.H1() # <h1> through h.H6()
h.A() # <a>
h.Img() # <img>
h.Input() # <input>
h.Form() # <form>
# ... and more
Text Content
Pass text as the first argument:
h.H1("Welcome")
h.P("This is a paragraph.")
h.Span("Inline text")
Nesting Elements
Use with blocks to nest elements:
with h.Div():
h.H1("Title")
with h.Div():
h.P("Nested paragraph")
Attributes
Pass HTML attributes as keyword arguments:
h.A("Click here", href="https://example.com", target="_blank")
h.Img(src="/image.png", alt="Description")
h.Input(type="text", placeholder="Enter text...")
Note: Use class_name instead of class (since class is a Python keyword):
h.Div(class_name="container")
Widgets
Widgets are higher-level components built on HTML. Import them from trellis.widgets:
from trellis import widgets as w
w.Button(text="Click me", on_click=handler)
Available Widgets
Button — Clickable button with variants:
w.Button(text="Click me", on_click=handler)
w.Button(text="Submit", variant="primary")
w.Button(text="Delete", variant="danger", disabled=True)
Variants: primary, secondary, outline, ghost, danger
Card — Container with border and padding:
with w.Card():
w.Label(text="Card content goes here")
Checkbox — Boolean toggle:
w.Checkbox(checked=mutable(state.enabled), label="Enable feature")
Column — Vertical flex container:
with w.Column(gap=16, padding=20):
w.Label(text="First")
w.Label(text="Second")
w.Label(text="Third")
Props: gap, padding, align, justify
Divider — Visual separator:
w.Divider()
Heading — Semantic heading text:
w.Heading(text="Welcome", level=1)
w.Heading(text="Section Title", level=2, color="#333")
Levels 1-6 render as <h1> through <h6>.
Label — Text display:
w.Label(text="Status: Ready", font_size=14)
NumberInput — Numeric input field:
w.NumberInput(value=mutable(state.count), min=0, max=100)
# With custom processing using callback():
def set_count(value: float) -> None:
state.count = max(0, min(100, int(value)))
w.NumberInput(value=callback(state.count, set_count))
ProgressBar — Progress indicator:
w.ProgressBar(value=50, min=0, max=100)
w.ProgressBar(loading=True) # Indeterminate loading state
Props: value, min, max, loading, disabled, color, height
Row — Horizontal flex container:
with w.Row(gap=8, align="center"):
w.Button(text="A")
w.Button(text="B")
w.Button(text="C")
Props: gap, padding, align, justify
Select — Dropdown selection:
w.Select(
value=mutable(state.choice),
options=[{"value": "a", "label": "Option A"}, {"value": "b", "label": "Option B"}],
)
Slider — Range input:
w.Slider(value=mutable(state.volume), min=0, max=100)
TextInput — Text input field:
w.TextInput(value=mutable(state.name), placeholder="Enter name")
Styling
Inline Styles
Use h.Style(...) for typed, IDE-autocompleted CSS with automatic px conversion for numeric values:
h.Div(
style=h.Style(
background_color="#f0f0f0",
padding=16, # → 16px
border_radius=8, # → 8px
)
)
h.Style() uses snake_case field names matching CSS properties: background_color, font_size, border_radius, etc.
Raw style dictionaries with kebab-case CSS names are also accepted as a fallback:
h.Div(style={"background-color": "#f0f0f0", "padding": "16px"})
CSS Classes
Use class_name to apply CSS classes:
h.Div(class_name="card card-primary")
Combining Styles
from trellis import component
from trellis import html as h
CARD_STYLE = h.Style(
background_color="#ffffff",
border="1px solid #e0e0e0",
border_radius=8,
padding=20,
box_shadow="0 2px 4px rgba(0,0,0,0.1)",
max_width=300,
)
TITLE_STYLE = h.Style(margin="0 0 8px 0", color="#333")
TEXT_STYLE = h.Style(margin=0, color="#666")
@component
def StyledCard():
with h.Div(style=CARD_STYLE):
h.H2("Card Title", style=TITLE_STYLE)
h.P("This is a styled card component.", style=TEXT_STYLE)
App = StyledCard
Combining HTML and Widgets
Mix HTML elements and widgets freely:
from trellis import component
from trellis import html as h
from trellis import widgets as w
@component
def SettingsPanel():
with h.Div(style=h.Style(padding=20, font_family="system-ui")):
h.H1("Settings", style=h.Style(margin_bottom=20))
with w.Column(gap=12):
with w.Row(gap=8, align="center"):
h.Span("Volume:", style=h.Style(width=100))
w.Slider(value=50, min=0, max=100)
h.Span("50%", style=h.Style(width=50))
with w.Row(gap=8, align="center"):
h.Span("Brightness:", style=h.Style(width=100))
w.Slider(value=75, min=0, max=100)
h.Span("75%", style=h.Style(width=50))
with w.Row(gap=8):
w.Button(text="Reset", variant="outline")
w.Button(text="Apply", variant="primary")
App = SettingsPanel
Layout Patterns
Centering Content
h.Div(style=h.Style(
display="flex",
justify_content="center",
align_items="center",
min_height=h.vh(100),
))
Grid Layout
h.Div(style=h.Style(
display="grid",
grid_template_columns="repeat(3, 1fr)",
gap=16,
))
Spacing
h.Div(style=h.Style(margin_bottom=16))
h.Div(style=h.Style(padding=h.padding(8, 16)))
Next Steps
- Patterns — Common patterns for state, callbacks, and lists
- Design Overview — How Trellis works under the hood