{"version":"v1.0","client_state":{"DocsNavigation.default":{"fields":{"query":""},"persist":"memory","send_to_server":true},"ColorScheme.default":{"fields":{"mode":"system"},"persist":"local","send_to_server":true}},"endpoints":{"action":"/_bretzel/action","sse":"/_bretzel/sse","refetch":"/_bretzel/refetch"},"csrf":"17075d9ccc068673917465187a90f9ce","page_id":"046cb32c65ac44bdae4a4b26e66c0d7c","address":""}

Lists and tables

Displaying a moving collection, without re-rendering the page. Two tools, and the choice between them hangs on one question: who writes the loop?

The question that settles it

It is the base layer's COLLECTION_OWNER rule, and it is gated. Whoever writes the `for` decides the shape of the API — because the rendering happens there.
Who writes the loopThe shape of the APIExample
the authora `with`, and the children insideui.each, filter_each, paginate_each
the componenta `render=` it calls per rowui.datatable, ui.select
the client (JS)neither one nor the other — the body is a templateui.file_upload

ui.each — the key, and why it counts

A bare `for` loop works… until an item carries client state. On re-render, idiomorph pairs the nodes by position: deleting the first shifts all the others, and the open accordion changes row. `ui.each` pushes a stable key per item, so the pairing follows IDENTITY.
for task in ui.each(tasks, key="id"):
    with ui.card():
        ui.text(task.title)
        ui.accordion(...)      # keeps its state

# `key=` also accepts a callable:
for t in ui.each(tasks, key=lambda t: t.uuid):
    ui.text(t.title)

filter_each — narrowing as you type

It sets a `bz-show` on every item, compared with what is typed. The filtering is therefore ENTIRELY client side: no round trip, and the items are HIDDEN, not removed — the DOM count does not move.
class Filter(ClientState):
    query: str = field(default="")

f = Filter()
ui.input(value=f.query, placeholder="Filter…")

for fruit in ui.filter_each(
    FRUITS,
    query=f.query,
    text=lambda x: x,          # what is searched
    key=lambda x: x,
    empty=lambda: ui.text("Nothing matches."),
):
    ui.text(fruit)
`empty=` is rendered too, and hidden as long as a row matches — otherwise it would take a round trip to know there is nothing.

paginate_each — a client-side window

The same mechanics: everything is rendered, only the current window is visible. `page` is a 1-indexed `ClientBinding` — so a `ui.pagination` drives it with no network.
class View(ClientState):
    page: int = field(default=1)

v = View()
for row in ui.paginate_each(ROWS, page=v.page,
                            per_page=20, key="id"):
    ui.text(row.name)
ui.pagination(value=v.page, total=len(ROWS),
              per_page=20)
Where the limit is
Everything is rendered: it is instant, and it only suits what fits in memory. Beyond a few hundred rows, `ui.datatable` is what you need — it paginates on the SERVER and renders only the page asked for.

ui.datatable — the loop belongs to the component

It sorts, filters, paginates, searches and exports. It is what decides which rows exist, so the author cannot write the `for` — they describe their columns, and pass a `render=` for the ones that are not text.
class AccountsTable(DatatableState, scope="session",
                    addressable=True):
    """One subclass PER table: the state is keyed by
    class, so sharing the base would share the sort
    and the page."""

ui.datatable(
    state=AccountsTable,
    columns=[
        ui.column("name", label="Name", sortable=True),
        ui.column("city", label="City", filter=True),
        ui.column("status", label="Status",
                  render=lambda v, row: ui.badge(v)),
    ],
    rows=load,             # a list, or a callable(Query)
    exportable=True,
    export_filename="accounts.csv",
    row_key="id",
)
`addressable=True` gives the view an ADDRESS: sorting, paginating or searching rewrites the URL. The link shares, bookmarks, and the browser's arrows go back and forth.
The parameter that changes everything
`rows=` accepts a list OR a callable that receives the `Query` (sort, page, search) and returns `(rows, total)`. It is the shape to take as soon as the source is a database: without it, one loads everything to display twenty.
The exact signatures:ui.* catalogue →