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#

A page stack shows one page at a time and remembers where you have been — add pages with .add(), switch with navigate(key), and step through history with back and forward. Reach for it for wizard or drill-down flows; Tabs when the user picks views directly.

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(grow=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 'column' (vertical stack).

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

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

# Grid
with ps.add("form", layout="grid", columns=["auto", 1], gap=8, horizontal_items="stretch"):
    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 — Column / Row parents use the layout kwargs below, grid-based parents use grid kwargs.

Column (vertical layout)

Used inside a Column, App, or any other container with a column layout. Children are arranged top-to-bottom, so horizontal aligns each child across the width and grow shares the vertical space. (vertical does not apply — the order of the children sets their top-to-bottom position.)

horizontal

Cross-axis placement of the widget: 'left', 'center', 'right', or 'stretch' to fill the available width.

grow

Claim and fill a share of the leftover vertical space (the layout direction). True or False.

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=.

Row (horizontal layout)

Used inside a Row or any other container with a row layout. Children are arranged left-to-right, so vertical aligns each child across the height and grow shares the horizontal space. (horizontal does not apply — the order of the children sets their left-to-right position.)

vertical

Cross-axis placement of the widget: 'top', 'center', 'bottom', or 'stretch' to fill the available height.

grow

Claim and fill a share of the leftover horizontal space (the layout direction). True or False.

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.

horizontal

Horizontal placement within the grid cell: 'left', 'center', 'right', or 'stretch' to fill the cell width.

vertical

Vertical placement within the grid cell: 'top', 'center', 'bottom', or 'stretch' to fill the cell height.

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.

Column, Row, 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.Row(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.Divider()
13
14    # ── Page container ────────────────────────────────────────────────────────
15    ps = bs.PageStack(grow=True, horizontal="stretch")
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.Divider()
22        with bs.Row(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):
27        bs.Label("Username")
28        bs.TextField(placeholder="your-username")
29        bs.Label("Theme")
30        bs.Select(options=["Light", "Dark", "System"])
31        bs.Label("Language")
32        bs.Select(options=["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()