Skip to main content

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.

Automatic Reactivity
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:

  1. When the component renders, it reads count
  2. Trellis records that this component depends on that value
  3. When the button updates count, Trellis marks the component as dirty
  4. 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

Todo List
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