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