Single-tier app#

A flat sidebar of top-level destinations — the everyday “dashboard” app. Each sidebar item is an authored page; selecting it shows that page’s content. With a single set of pages, no workspace rail appears.

Single-tier dashboard app — light theme Single-tier dashboard app — dark theme

How it works#

page_nav() declares the sidebar as an authored page list and returns a handle; each add_page(key, *, text, icon) registers a sidebar item and its content page together, and returns a context manager — widgets inside the with block are parented to that page. A page is a column, so set padding / gap on add_page (no inner wrapper). pin_to_footer=True pins an item (Settings, Account) to the bottom of the sidebar. navigate(key) selects the starting page.

with shell.page_nav() as nav:
    with nav.add_page("overview", text="Overview", icon="speedometer2", padding=20):
        bs.Label("Overview", font="heading-lg")
shell.navigate("overview")

Example#

 1"""Single-tier app — a flat list of top-level destinations (an analytics dashboard).
 2
 3``page_nav()`` declares the sidebar as an authored page list; each
 4``nav.add_page`` registers a sidebar item and its content together. A page IS a
 5column, so set ``padding`` / ``gap`` on ``add_page`` and drop the inner wrapper.
 6The first ``navigate`` picks the starting page; with one sidebar no rail appears.
 7"""
 8import bootstack as bs
 9
10with bs.AppShell(title="Acme Analytics", size=(900, 580)) as shell:
11    with shell.add_toolbar() as bar:
12        with bar.add_menu("File") as file:
13            file.add_action("New", shortcut="Mod+N", on_click=lambda: None)
14            file.add_action("Open", shortcut="Mod+O", on_click=lambda: None)
15            file.add_divider()
16            file.add_action("Quit", shortcut="Mod+Q", on_click=shell.close)
17        with bar.add_menu("View") as view:
18            view.add_action("Refresh", shortcut="Mod+R", on_click=lambda: None)
19        bar.add_spacer()
20        bar.add_button(icon="search", on_click=lambda: None)
21        bar.add_theme_toggle()
22
23    with shell.page_nav() as nav:
24        with nav.add_page("overview", text="Overview", icon="speedometer2", padding=20, gap=12):
25            bs.Label("Overview", font="heading-lg")
26            with bs.Grid(columns=3, gap=12, horizontal="stretch"):
27                for label, value in (("Revenue", "$48.2k"), ("Orders", "1,204"), ("Visitors", "18.9k")):
28                    with bs.Card(padding=16, gap=4):
29                        bs.Label(label, font="caption")
30                        bs.Label(value, font="heading-md")
31
32        with nav.add_page("reports", text="Reports", icon="bar-chart", padding=20, gap=8):
33            bs.Label("Reports", font="heading-lg")
34            bs.Label("Build and schedule reports.")
35
36        with nav.add_page("customers", text="Customers", icon="people", padding=20, gap=8):
37            bs.Label("Customers", font="heading-lg")
38            bs.Label("1,204 active accounts.")
39
40        with nav.add_page("settings", text="Settings", icon="gear", pin_to_footer=True, padding=20, gap=8):
41            bs.Label("Settings", font="heading-lg")
42
43    shell.navigate("overview")
44
45shell.run()

When to use#

Reach for the single-tier app when you have a handful of independent destinations and no need to sub-group them. If the destinations fall into clear categories, add section labels — see Grouped sidebar. If the sidebar is really a list of records (messages, devices), use Master–detail (list) instead. If the app has several distinct areas each with their own sidebar, give each a workspace — see Workspaces.