Skip to main content

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​

Styled Card
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