Scrolling
A site page scrolls whole. A tool does not: its sidebar stays, its header stays, and it is the central area that moves. Bretzel does both, and the choice is made once, at the shell.The default: the document scrolls
Nothing to write. A page grows, the browser scrolls. It is the model of most sites, and of the majority of the repository's examples.@layout
def coque() -> None:
with ui.vstack(gap="md", classes="p-8"):
ui.heading("Mon app")
ui.outlet()
The other: the document is frozen
`ui.viewport` is a full-screen frame, out of the flow: IT does not scroll. `ui.pane` is a region that takes the remaining space and scrolls on its own. That is exactly what this documentation's shell does.@layout
def shell() -> None:
with ui.viewport(): # does NOT scroll
with ui.sidebar(collapsible="rail"):
... # stays in place
with ui.pane(padding="lg"):
ui.outlet() # scrolls, on its own
`ui.viewport` is a `Flex`: it accepts `direction`, `gap`, `align`, `justify`. By default its children are in a row — sidebar on the left, panel on the right.Which one to pick
| If | Then |
|---|---|
| a content page one reads top to bottom | the default — the document scrolls |
| a sidebar or a header that must stay visible | `ui.viewport` + `ui.pane` |
| two lists side by side that scroll independently | `ui.viewport` + two `ui.pane` |
| one hesitates | the default. Freezing the document is a commitment: everything that overflows must live in a region |
What freezing the document implies
The main trap
Everything that overflows must be INSIDE a scrolling region. Content placed directly in the `ui.viewport` is cut off, with no scrollbar to catch it — the frame does not scroll, that is its definition.
What surprises you when scripting
`document.body.scrollHeight` is 0 in this model, and `window.scrollTo` does nothing: it is not the document that carries the scrolling. A script — or a test — that wants to scroll must target the region. Measured while writing this documentation's probes, where the instrument first returned 0 px of total height.