| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [View Raw Code] [Original HTTPS Page] |
Small examples that show the three navigators (stack, tab, and drawer), how screens receive their params as ordinary arguments, and how navigators nest. Every screen is a @pn.component, and every navigator is a module-level value rendered by [NavigationContainer][pythonnative.NavigationContainer].
For the conceptual model and the full API, see the Navigation guide and the Navigation API reference.
Create a project with pn init navigation-demo, then cd navigation-demo. Replace app/main.py with the Stack, Tab, Drawer, or Nesting snippet below. Each includes its imports and defines App, the project's root component.
From the project root, run pn preview to open the app in your browser. To run on a device or simulator, leave the preview running and use pn run android or pn run ios in another terminal from the same directory. See the Browser preview guide and Development workflow for more options.
A pushable, poppable stack: the default for "go from screen A to screen B with a back button."
import pythonnative as pn
@pn.component
def HomeScreen() -> pn.Node:
nav = pn.use_navigation()
return pn.Column(
pn.Text("Home", style={"font_size": 28, "bold": True}),
pn.Button("View profile", on_press=lambda: nav.push(ProfileScreen(user_id=42))),
style={"gap": 12, "padding": 16},
)
@pn.component
def ProfileScreen(user_id: int) -> pn.Node:
nav = pn.use_navigation()
pn.use_screen_options(title=f"User {user_id}")
return pn.Column(
pn.Text(f"User #{user_id}", style={"font_size": 24}),
pn.Button("Back", on_press=nav.go_back),
style={"gap": 12, "padding": 16},
)
Root = pn.StackNavigator(
pn.Screen(HomeScreen, title="Home"),
ProfileScreen,
)
@pn.component
def App() -> pn.Node:
return pn.NavigationContainer(Root)ProfileScreen(user_id=42) is an ordinary call, so your editor autocompletes user_id and your type checker rejects a missing or misspelled param. nav.push(...) always adds a new screen, nav.navigate(...) returns to an existing ProfileScreen if there is one, and nav.go_back() pops one screen. To replace the entire history (after signing in, for example), use nav.reset(HomeScreen()).
A persistent native tab bar, with one screen per tab. Each tab keeps its own state across switches.
import pythonnative as pn
@pn.component
def FeedScreen() -> pn.Node:
return pn.Text("Feed", style={"padding": 16, "font_size": 24})
@pn.component
def SearchScreen() -> pn.Node:
query, set_query = pn.use_state("")
return pn.Column(
pn.TextInput(value=query, on_change=set_query, placeholder="Search..."),
pn.Text(f"Results for: {query}"),
style={"gap": 12, "padding": 16},
)
@pn.component
def SettingsScreen() -> pn.Node:
return pn.Text("Settings", style={"padding": 16, "font_size": 24})
Tabs = pn.TabNavigator(
pn.Screen(FeedScreen, title="Feed", tab_bar_icon="house"),
pn.Screen(SearchScreen, title="Search", tab_bar_icon="search"),
pn.Screen(SettingsScreen, title="Settings", tab_bar_icon="settings"),
)
@pn.component
def App() -> pn.Node:
return pn.NavigationContainer(Tabs)The search box keeps its query when the user switches to Settings and back: tab screens stay mounted unless a screen sets unmount_on_blur=True. Tab screens must be callable without arguments, so give any tab parameters defaults.
A side drawer for primary navigation in larger apps.
import pythonnative as pn
from pythonnative.navigation import DrawerNavigation
@pn.component
def MenuButton() -> pn.Node:
nav = pn.use_navigation()
assert isinstance(nav, DrawerNavigation)
return pn.Button("Menu", on_press=nav.toggle_drawer)
@pn.component
def InboxScreen() -> pn.Node:
return pn.Column(
MenuButton(),
pn.Text("Inbox", style={"font_size": 24}),
style={"gap": 12, "padding": 16},
)
@pn.component
def SentScreen() -> pn.Node:
return pn.Column(
MenuButton(),
pn.Text("Sent", style={"font_size": 24}),
style={"gap": 12, "padding": 16},
)
Drawer = pn.DrawerNavigator(
pn.Screen(InboxScreen, title="Inbox"),
pn.Screen(SentScreen, title="Sent"),
)
@pn.component
def App() -> pn.Node:
return pn.NavigationContainer(Drawer)The drawer is drawn in Python and has no navigation bar of its own, so each screen renders its own Menu button. The drawer opens with a swipe from the leading edge or with toggle_drawer() on the [DrawerNavigation][pythonnative.navigation.DrawerNavigation] handle. use_navigation() is typed as the base Navigation, so the isinstance check narrows it to the drawer handle.
Navigators compose: a navigator is a valid screen of another navigator. A common shape is a root stack whose first screen is a tab navigator, so detail screens push over the tab bar:
import pythonnative as pn
@pn.component
def PostLink(id: int) -> pn.Node:
nav = pn.use_navigation()
return pn.Button(f"Post {id}", on_press=lambda: nav.push(PostScreen(id=id)))
@pn.component
def FeedScreen() -> pn.Node:
return pn.Column(
*(PostLink(id=post_id).with_key(post_id) for post_id in range(1, 4)),
style={"gap": 8, "padding": 16},
)
@pn.component
def ProfileScreen(user: str = "me") -> pn.Node:
return pn.Text(f"Profile: {user}", style={"padding": 16, "font_size": 24})
@pn.component
def PostScreen(id: int) -> pn.Node:
nav = pn.use_navigation()
pn.use_screen_options(title=f"Post {id}")
return pn.Column(
pn.Text(f"Post #{id} by ada"),
pn.Button("View author", on_press=lambda: nav.navigate(ProfileScreen(user="ada"))),
style={"gap": 12, "padding": 16},
)
Tabs = pn.TabNavigator(
pn.Screen(FeedScreen, title="Feed", tab_bar_icon="house", path="feed"),
pn.Screen(ProfileScreen, title="Profile", tab_bar_icon="user", path="u/{user}"),
name="Main",
)
Root = pn.StackNavigator(
pn.Screen(Tabs, header_shown=False),
pn.Screen(PostScreen, path="posts/{id}"),
)
@pn.component
def App() -> pn.Node:
return pn.NavigationContainer(Root, link_prefixes=["navdemo://"])nav.push(PostScreen(id=...)) from the feed tab reaches the root stack, because a target the tab navigator doesn't know is handed to its parent. View author navigates back down: the root stack finds ProfileScreen inside Tabs, pops the post, and selects the profile tab with user="ada". The same static tree answers deep links, so navdemo://posts/2 opens PostScreen(id=2) above the feed and navdemo://u/ada opens the profile tab.
When you need to run something only while a screen is visible (a camera, GPS, or an animation), use [use_focus_effect][pythonnative.use_focus_effect]:
from collections.abc import Callable
@pn.component
def CameraScreen() -> pn.Node:
def start() -> Callable[[], None]:
start_camera()
return stop_camera
pn.use_focus_effect(start, [])
return pn.View()The cleanup runs as soon as the user navigates away, even if the screen stays mounted.
To try this with the Tab navigator example, add CameraScreen and stand-ins for start_camera and stop_camera, and replace pn.Screen(SettingsScreen, ...) with pn.Screen(CameraScreen, title="Camera", tab_bar_icon="camera"). Run pn preview, then switch between the Camera and Feed tabs to trigger the effect and its cleanup.
| Back | FazBrowse Home | New Git URL |