Skip to content

Navigation

Navigation is how users move between different views in your application. bootstack provides several patterns for organizing and switching between content areas.

This guide covers:

  • AppShell — the fastest way to get toolbar + sidebar + pages
  • SideNav — standalone sidebar navigation with display modes and groups
  • Tabs — category-based switching with Notebook
  • Stacked views — flow-based navigation with PageStack
  • Custom sidebar — rolling your own navigation panel

Choosing a navigation pattern

Pattern Best for Widget
AppShell Most desktop apps — toolbar, sidebar, and pages in one call AppShell
SideNav + PageStack Custom layouts where you control toolbar/content placement SideNav + PageStack
Tabs Category switching, settings panels, dashboards Notebook
Stacked views Wizards, multi-step flows, drill-down navigation PageStack
Custom sidebar Unusual layouts where built-in widgets don't fit PageStack + custom sidebar

For most applications, start with AppShell. Drop down to SideNav + PageStack if you need more control over layout, or to a custom sidebar if your requirements are truly unique.


AppShell

AppShell is the fastest way to build a navigation-based application. It wires together a Toolbar, SideNav, and PageStack automatically:

import bootstack as bs

shell = bs.AppShell(title="My App", minsize=(1000, 650))

# Each add_page() creates a nav item and returns a Frame for content
home = shell.add_page("home", text="Home", icon="house")
bs.Label(home, text="Welcome!").pack(padx=20, pady=20)

docs = shell.add_page("docs", text="Documents", icon="file-earmark-text")
bs.Label(docs, text="Your documents.").pack(padx=20, pady=20)

# Toolbar buttons appear on the right side
shell.toolbar.add_button(icon="sun", command=bs.toggle_theme)

shell.mainloop()
App Shell
shell.add_group("files", text="Files", icon="folder")
shell.add_page("local", text="Local", icon="hdd", group="files")
shell.add_page("cloud", text="Cloud", icon="cloud", group="files")

shell.add_page("settings", text="Settings", icon="gear", is_footer=True)

Programmatic navigation

shell.navigate("settings")

shell.on_page_changed(lambda e: print(f"Now on: {shell.current_page}"))

When to use AppShell

  • You want the standard toolbar + sidebar + pages layout
  • You want navigation wired to pages automatically
  • You want a quick scaffold without manual widget plumbing

When to use something else

  • You need a custom toolbar position or multiple toolbars — use SideNav + PageStack directly
  • You need horizontal tabs — use Notebook
  • You need a plain window — use App

SideNav

SideNav is the standalone sidebar navigation widget. Use it when you need sidebar navigation but want full control over the surrounding layout:

import bootstack as bs

app = bs.App(title="SideNav Demo", minsize=(900, 600))

nav = bs.SideNav(app)
nav.pack(side="left", fill="y")

nav.add_item("home", text="Home", icon="house")
nav.add_item("docs", text="Documents", icon="file-earmark-text")
nav.add_item("settings", text="Settings", icon="gear")

nav.select("home")

nav.on_selection_changed(lambda e: print(f"Selected: {e.data['key']}"))

app.mainloop()

Wiring SideNav to PageStack

When using SideNav outside of AppShell, you wire it to a PageStack manually:

import bootstack as bs

app = bs.App(title="SideNav + PageStack", minsize=(900, 600))

# Sidebar
nav = bs.SideNav(app)
nav.pack(side="left", fill="y")

# Content area
stack = bs.PageStack(app)
stack.pack(side="left", fill="both", expand=True)

# Add nav items and pages
nav.add_item("home", text="Home", icon="house")
home = stack.add("home", padding=20)
bs.Label(home, text="Welcome!").pack()

nav.add_item("settings", text="Settings", icon="gear")
settings = stack.add("settings", padding=20)
bs.Label(settings, text="Settings page").pack()

# Wire selection to navigation
nav.on_selection_changed(lambda e: stack.navigate(e.data["key"]))

nav.select("home")
app.mainloop()

Display modes

Mode Description
expanded Full width with icon and text (default)
compact Narrow, icon-only; groups show popup menus
minimal Hidden until toggled open
nav = bs.SideNav(app, display_mode="compact")

# Toggle between modes
nav.toggle_pane()
nav.set_display_mode("expanded")

Groups

Groups expand/collapse in expanded mode and show a popup menu in compact mode:

nav.add_group("files", text="Files", icon="folder")
nav.add_item("local", text="Local", icon="hdd", group="files")
nav.add_item("cloud", text="Cloud", icon="cloud", group="files")

Tabs with Notebook

Use Notebook when users need random access to related views. Tabs are visible and clickable, making it easy to switch between categories.

import bootstack as bs

app = bs.App(size=(300, 200))

notebook = bs.Notebook(app, accent="primary")

notebook.pack(fill="both", expand=True, padx=10, pady=10)

# Create tabs
home = notebook.add(text="Home", key="home")
settings = notebook.add(text="Settings", key="settings")
about = notebook.add(text="About", key="about")

# Add content to each tab
bs.Label(home, text="Welcome to the application").pack(anchor="w")
bs.Label(settings, text="Configure your preferences here").pack(anchor="w")
bs.Label(about, text="Version 1.0").pack(anchor="w")

app.mainloop()
Navigation Tabs

When to use tabs

  • Users need to switch freely between views
  • Views are peers (no hierarchy or sequence)
  • You have 2-7 top-level sections

When to avoid tabs

  • You have many sections (7+) — use SideNav or AppShell
  • Navigation is sequential — use PageStack instead
  • Views have parent-child relationships — use PageStack with back/forward

Stacked views with PageStack

Use PageStack when navigation is sequential or flow-based. It maintains a history stack, enabling back/forward navigation like a web browser.

import bootstack as bs

app = bs.App()

stack = bs.PageStack(app)
stack.pack(fill="both", expand=True, padx=10, pady=10)

# Create pages
welcome = stack.add("welcome", padding=20)
details = stack.add("details", padding=20)
confirm = stack.add("confirm", padding=20)

# Welcome page
bs.Label(welcome, text="Welcome! Let's get started.").pack(anchor="w", pady=(0, 10))
bs.Button(welcome, text="Continue", command=lambda: stack.navigate("details")).pack()

# Details page
bs.Label(details, text="Enter your details").pack(anchor="w", pady=(0, 10))
bs.Button(details, text="Back", command=stack.back).pack(side="left", padx=(0, 5))
bs.Button(details, text="Continue", command=lambda: stack.navigate("confirm")).pack(side="left")

# Confirm page
bs.Label(confirm, text="All done!").pack(anchor="w", pady=(0, 10))
bs.Button(confirm, text="Back", command=stack.back).pack()

stack.navigate("welcome")
app.mainloop()

When to use PageStack

  • Multi-step wizards or forms
  • Drill-down navigation (list → detail → edit)
  • Back/forward improves usability
Method Description
navigate(key) Push a new page onto the history stack
navigate(key, replace=True) Replace current page (no back)
back() Go to previous page
forward() Go to next page (after back)
can_back() Check if back is available
can_forward() Check if forward is available

Combining patterns

You can combine navigation patterns. For example, an AppShell for top-level sections, with tabs inside one of the pages:

import bootstack as bs

shell = bs.AppShell(title="Combined Navigation", minsize=(1000, 650))

# Simple page
home = shell.add_page("home", text="Home", icon="house")
bs.Label(home, text="Dashboard", font="heading-xl").pack(anchor="w", padx=20, pady=20)

# Page with tabs inside
settings = shell.add_page("settings", text="Settings", icon="gear")

tabs = bs.Notebook(settings)
tabs.pack(fill="both", expand=True)

general = tabs.add(text="General", key="general", padding=16)
bs.Label(general, text="General settings").pack(anchor="w")

advanced = tabs.add(text="Advanced", key="advanced")
bs.Label(advanced, text="Advanced options").pack(anchor="w")

shell.mainloop()
Combining Patterns

Custom sidebar

For layouts where SideNav doesn't fit, you can build a custom sidebar with buttons controlling a PageStack:

import bootstack as bs

app = bs.App(title="Custom Sidebar", minsize=(800, 500))

# Sidebar
sidebar = bs.Frame(app, padding=10, width=200)
sidebar.pack(side="left", fill="y")

# Content
stack = bs.PageStack(app)
stack.pack(side="left", fill="both", expand=True)

# Pages
for key in ["dashboard", "users", "settings"]:
    page = stack.add(key, padding=20)
    bs.Label(page, text=key.title(), font="heading-xl").pack(anchor="w")

# Navigation buttons with active tracking
nav_buttons = {}

for label, key in [("Dashboard", "dashboard"), ("Users", "users"), ("Settings", "settings")]:
    btn = bs.Button(
        sidebar, text=label, width=20,
        accent="secondary", variant="outline",
        command=lambda k=key: stack.navigate(k),
    )
    btn.pack(fill="x", pady=2)
    nav_buttons[key] = btn

def on_page_changed(event):
    current = event.data.get("page")
    for key, btn in nav_buttons.items():
        if key == current:
            btn.configure(accent="primary", variant=None)
        else:
            btn.configure(accent="secondary", variant="outline")

stack.on_page_changed(on_page_changed)
stack.navigate("dashboard")

app.mainloop()
Custom Sidebar

This is significantly more code than using SideNav or AppShell, so only use this approach when you need a layout that the built-in widgets can't provide.


Best practices

Keep navigation consistent

  • Place navigation in the same location across all views
  • Use consistent labels and icons
  • Don't mix tabs and sidebar for the same level of navigation

Provide context

  • Show the current location (active tab, highlighted sidebar item)
  • Use breadcrumbs for deep hierarchies
  • Enable back navigation when appropriate

Handle deep linking

If your app needs to open a specific view programmatically:

# AppShell
shell.navigate("users")

# PageStack directly
stack.navigate("user-detail", data={"user_id": 42})

Preserve state

When switching views, consider whether state should be preserved:

  • Notebook tabs preserve state by default (widgets stay alive)
  • PageStack pages also preserve state (pages aren't destroyed)
  • For forms, save state explicitly before navigating away

Additional resources