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.)
|
Cross-axis placement of the widget: |
|
Claim and fill a share of the leftover vertical space (the layout
direction). |
|
External spacing in pixels. Accepts an integer (equal on all
sides), a 2-tuple |
|
Horizontal external spacing (left and right). Accepts an integer
or a 2-tuple |
|
Vertical external spacing (top and bottom). Accepts an integer
or a 2-tuple |
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.)
|
Cross-axis placement of the widget: |
|
Claim and fill a share of the leftover horizontal space (the layout
direction). |
|
External spacing in pixels. Accepts an integer (equal on all
sides), a 2-tuple |
|
Horizontal external spacing (left and right). Accepts an integer
or a 2-tuple |
|
Vertical external spacing (top and bottom). Accepts an integer
or a 2-tuple |
Grid
Used inside a Grid container.
|
Zero-based row and column indices. |
|
Number of rows or columns to span. |
|
Horizontal placement within the grid cell: |
|
Vertical placement within the grid cell: |
|
External spacing in pixels. Accepts an integer, a 2-tuple
|
|
Horizontal external spacing. Accepts an integer or |
|
Vertical external spacing. Accepts an integer or |
See also#
ScrollView —
scrollable container with vertical and/or horizontal scrollbars.
SplitView —
resizable split container with draggable sashes.
Accordion —
collapsible section container.
API#
The complete reference for PageStack and its
StackPage handles lives on the
Widgets API page. At a glance:
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()