Core Concepts
The essential concepts you need to understand Trellis.
Components
Components are functions that describe UI. Decorate a function with @component to make it a component:
from trellis import component
from trellis import html as h
@component
def Greeting() -> None:
h.H1("Hello, world!")
Components don't return anything—they add elements to the UI by calling other components and HTML elements.
Nesting with with Blocks
Use Python's with statement to nest elements:
@component
def Card() -> None:
with h.Div(style=h.Style(border="1px solid #ccc", padding=16)):
h.H2("Card Title")
h.P("Card content goes here.")
The with block collects everything inside it as children of the outer element.
Component Arguments
Components can accept arguments just like regular functions:
@component
def UserCard(name: str, email: str) -> None:
with h.Div():
h.H3(name)
h.P(email)
Use it like this:
UserCard(name="Alice", email="alice@example.com")
Traits
Elements support fluent method chaining for configuration. The most common trait is .key(), which sets a reconciliation key:
@component
def ItemList(items: list[str]) -> None:
for item in items:
h.Div(item).key(item) # Key helps Trellis track items across renders
Keys are important when rendering dynamic lists—they help Trellis match elements across renders, preserving state and avoiding unnecessary DOM updates.
State
For small local values, use state_var() inside a component:
from trellis import component, state_var
from trellis import html as h
@component
def Counter() -> None:
count = state_var(0)
h.P(f"Count: {count}")
The state persists across re-renders. Each time the component renders, it gets the same slot-local state object. The @component decorator applies an AST transform that rewrites reads and writes of count to go through .value automatically, so your code stays clean.
For more structured or shared state, define a class that inherits from Stateful:
from dataclasses import dataclass
from trellis import Stateful
@dataclass
class CounterState(Stateful):
count: int = 0
def increment(self) -> None:
self.count += 1
Reactivity
When you read a state property in a component, Trellis tracks that dependency. When the property changes, the component re-renders automatically.
from trellis import component, state_var
from trellis import widgets as w
@component
def Counter():
count = state_var(0)
def increment():
nonlocal count
count += 1
with w.Column():
w.Label(text=f"Count: {count}")
w.Button(text="Increment", on_click=increment)
App = Counter
How it works:
- When the component renders, it reads
count - Trellis records that this component depends on that value
- When the button updates
count, Trellis marks the component as dirty - On the next render cycle, only dirty components re-render
You don't subscribe to changes or call update functions—just read and write state normally.
Callbacks
Callbacks are functions that run when events happen. Pass them as arguments to components:
Button(text="Click me", on_click=handle_click)
Callbacks typically modify state:
from trellis import Button, component, state_var
from trellis import html as h
@component
def Form() -> None:
submitted = state_var(False)
def submit() -> None:
nonlocal submitted
submitted = True
if submitted:
h.P("Form submitted!")
else:
Button(text="Submit", on_click=submit)
Callbacks with Arguments
For callbacks that need arguments, use a lambda:
@dataclass
class ListState(Stateful):
items: list[str] = field(default_factory=lambda: ["A", "B", "C"])
def remove(self, item: str) -> None:
self.items.remove(item)
@component
def ItemList() -> None:
state = ListState()
for item in state.items:
with h.Div():
h.Span(item)
Button(text="Remove", on_click=lambda i=item: state.remove(i))
Note the i=item default argument—this captures the current value of item in the lambda.
Putting It Together
from dataclasses import dataclass, field
from trellis import Stateful, component
from trellis import html as h
from trellis import widgets as w
@dataclass
class TodoState(Stateful):
items: list[str]
next_id: int
def add(self):
self.items.append(f"Task {self.next_id}")
self.next_id += 1
def remove(self, item: str):
self.items.remove(item)
@component
def TodoList():
state = TodoState(items=["Learn Trellis", "Build an app"], next_id=3)
with w.Column():
w.Heading(text="Todo List")
for item in state.items:
with w.Row():
w.Label(text=item)
w.Button(text="×", on_click=lambda i=item: state.remove(i))
w.Button(text="Add Task", on_click=state.add)
App = TodoList
Next Steps
- Building UIs — HTML elements, widgets, and styling
- Patterns — Common patterns for state and components