PageStack#

A browser-style navigation container that shows one page at a time. Add named pages with .add(), place child widgets inside each page using the returned context manager, then call navigate() to switch between them. Forward and back navigation is tracked automatically.

Usage#

Adding pages#

Call .add(key) once per page. Use the returned StackPage as a context manager — child widgets created inside the with block are placed on that page.

ps = bs.PageStack(fill="both", expand=True)

with ps.add("home"):
    bs.Label("Home page content")

with ps.add("settings"):
    bs.Label("Settings page content")

ps.navigate("home")

Back and forward#

Every navigate() call pushes a history entry. Use back() and forward() to move through history, guarded by can_back and can_forward.

ps.navigate("home")
ps.navigate("settings")

ps.can_back    # True
ps.back()      # → "home"

ps.can_forward # True
ps.forward()   # → "settings"

Page layout modes#

Each page has an independent internal layout controlled by the layout= argument of add(). The default is 'vstack' (vertical stack).

# Vertical stack (default)
with ps.add("home", layout="vstack", gap=8):
    bs.Label("Row 1")
    bs.Label("Row 2")

# Horizontal stack
with ps.add("toolbar", layout="hstack", gap=6):
    bs.Button("Cut")
    bs.Button("Copy")
    bs.Button("Paste")

# Grid
with ps.add("form", layout="grid", columns=["auto", 1], gap=8, sticky_items="ew"):
    bs.Label("Username")
    bs.TextField()
    bs.Label("Password")
    bs.PasswordField()

Passing data to pages#

Pass an optional data dict to navigate(). The dict is forwarded to the page’s on_page_mount event payload so the receiving page can act on it.

ps.navigate("detail", data={"record_id": 42})

def on_mount(event):
    record_id = event.data.get("record_id")

ps.on_page_mount(on_mount)

Events#

on_page_change fires after every navigation. on_page_mount fires when a page becomes visible. Both receive a PageChangeEvent with context about the transition — the page and prev_page keys, the nav direction, and any data passed to navigate().

def on_change(event):
    page = event.page        # current page key
    prev = event.prev_page   # previous page key (or None)
    nav  = event.nav         # 'push', 'back', or 'forward'

ps.on_page_change(on_change)

# Stream form — compose with operators
ps.on_page_change().listen(lambda e: print(e.page))

Introspection#

ps.current        # key of the visible page, or None
ps.can_back       # True if back() would succeed
ps.can_forward    # True if forward() would succeed
ps.page_keys()    # ('home', 'settings', 'about')

Widget sizing#

All widgets accept self-placement kwargs via **kwargs. The parent container determines which options apply — stack-based parents use stack kwargs, grid-based parents use grid kwargs. Unrecognised keys are silently ignored.

Stack#

Used inside VStack, HStack, App, and other stack containers.

fill

Fill direction: 'x', 'y', 'both', or 'none'.

expand

Grow to consume extra space in the parent. True or False.

anchor

Alignment when the widget does not fill the available slot: 'n', 's', 'e', 'w', 'center', 'nw', etc.

margin

External spacing in pixels. Accepts an integer (equal on all sides), a 2-tuple (horizontal, vertical), or a 4-tuple (left, top, right, bottom).

margin_x

Horizontal external spacing (left and right). Accepts an integer or a 2-tuple (left, right) for asymmetric spacing. Overrides the horizontal component of margin=.

margin_y

Vertical external spacing (top and bottom). Accepts an integer or a 2-tuple (top, bottom) for asymmetric spacing. Overrides the vertical component of margin=.

Grid#

Used inside a Grid container.

row / column

Zero-based row and column indices.

rowspan / columnspan

Number of rows or columns to span.

sticky

Alignment and fill within the grid cell. Any combination of 'n', 's', 'e', 'w' — e.g. 'ew' stretches horizontally, 'nsew' fills the entire cell.

margin

External spacing in pixels. Accepts an integer, a 2-tuple (horizontal, vertical), or a 4-tuple (left, top, right, bottom).

margin_x

Horizontal external spacing. Accepts an integer or (left, right).

margin_y

Vertical external spacing. Accepts an integer or (top, bottom).

See also#

ScrollView — scrollable container with vertical and/or horizontal scrollbars.

SplitView — resizable split container with draggable sashes.

Accordion — collapsible section container.

VStack, HStack, and Grid — non-navigating layout containers.

API#

The complete reference for PageStack and its StackPage handles lives on the Widgets API page. At a glance:

PageStack

A browser-style navigation container showing one page at a time.

StackPage

A handle for one page — both a layout context and a navigation target.

Full Example#

 1
 2with bs.App(title="PageStack", size=(680, 500), padding=20, gap=12) as app:
 3
 4    bs.Label("PageStack — one page visible at a time", font="heading-md")
 5
 6    # ── Navigation bar ────────────────────────────────────────────────────────
 7    with bs.HStack(gap=6):
 8        btn_home = bs.Button("Home", accent="primary")
 9        btn_settings = bs.Button("Settings")
10        btn_about = bs.Button("About")
11
12    bs.Separator()
13
14    # ── Page container ────────────────────────────────────────────────────────
15    ps = bs.PageStack(fill="both", expand=True)
16
17    with ps.add("home", gap=12):
18        bs.Label("Welcome", font="heading-lg")
19        bs.Label("This is the Home page.")
20        bs.Label("Use the buttons above to switch pages.")
21        bs.Separator()
22        with bs.HStack(gap=8):
23            bs.Button("Get Started", accent="primary")
24            bs.Button("Learn More", variant="outline")
25
26    with ps.add("settings", layout="grid", columns=["auto", 1], gap=8, sticky_items="ew"):
27        bs.Label("Username")
28        bs.TextField(placeholder="your-username")
29        bs.Label("Theme")
30        bs.Select(values=["Light", "Dark", "System"])
31        bs.Label("Language")
32        bs.Select(values=["English", "Spanish", "French"])
33
34    with ps.add("about", gap=12):
35        bs.Label("About bootstack", font="heading-lg")
36        bs.Label("A batteries-included Python desktop UI framework.")
37        bs.Label("Build modern desktop apps without touching Tkinter.")
38
39    ps.navigate("home")
40
41    btn_home.on_click(lambda e: ps.navigate("home"))
42    btn_settings.on_click(lambda e: ps.navigate("settings"))
43    btn_about.on_click(lambda e: ps.navigate("about"))
44
45app.run()