Curiosity

Routing and Navigation

Curiosity workspaces are single-page apps. The URL hash (#/path?key=value) is the entire state — Router resolves it to a view, and App.Sidebar exposes hooks to inject your own navigation buttons.

Registering routes

Router.Register(path, handler) maps a route to an action. Register inside the same place the built-in routes are defined — the partial App.DefaultRouting.Define() method.

public static partial class App
{
    public static class CustomRouting
    {
        public static void Define()
        {
            Router.Register("devices", state => App.ShowDefault(new DevicesListView()));

            Router.Register("device", state =>
            {
                var key = state.TryGetValue("key", out var k) ? k : null;
                if (key is null) { Router.Navigate("#/devices"); return; }
                App.ShowDefault(new DeviceDetailView(key));
            });
        }
    }
}

Call App.CustomRouting.Define() from the same module-level initialization that runs App.DefaultRouting.Define().

state is a Dictionary<string,string> of the hash query parameters — #/device?key=MBP14 becomes state["key"] == "MBP14".

Router.Navigate("#/devices");                    // Just the path
Router.Navigate("#/device", ("key", "MBP14"));   // Path + params
Router.Back();                                   // Browser history back

For external links, fall back to window.location.hrefRouter is for app-internal navigation only.

Customizing the sidebar

Attach to App.Sidebar lifecycle events to add buttons. The two most useful ones:

Event Fires when
OnSidebarRebuild_BeforeFooter After main entries, before the footer (Settings, Help).
OnSidebarRebuild_AfterHeader After the workspace logo, before the search box.
App.Sidebar.OnSidebarRebuild_BeforeFooter += (sidebar, mode, tracker) =>
{
    sidebar.AddContent(
        new SidebarButton("devices", UIcons.Box, "Devices")
            .OnClick(() => Router.Navigate("#/devices")));

    sidebar.AddContent(
        new SidebarButton("cases", UIcons.Headset, "Cases")
            .OnClick(() => Router.Navigate("#/cases")));
};

mode tells you whether the sidebar is in expanded or collapsed form — useful if you want to hide labels in collapsed mode.

Highlighting the active route

SidebarButton.IsActiveWhen(path) keeps the button highlighted when the route matches:

new SidebarButton("devices", UIcons.Box, "Devices")
    .OnClick(() => Router.Navigate("#/devices"))
    .IsActiveWhen("devices");

Hiding the default navigation

For full-screen views (onboarding flows, fullscreen visualizations) call:

App.HideNavigation();
App.Show(new MyFullScreenView(), title: "Onboarding");

To restore: App.ShowNavigation().

Patterns

  • Tabs as routes. A Tabs component can drive Router.Navigate on tab change so the URL reflects the visible tab. Deep links survive reload.
  • Modals don't change the route. Use Modal.Show(...) for transient UI; reserve routes for state worth bookmarking.
  • Parameter validation. Validate state values inside the route handler before passing them to a view — bad URLs are common (users share them).
© 2026 Curiosity. All rights reserved.