Layout

Vercel Sidebar

Vercel-style sidebar with multi-panel navigation.

Overview

A layout built on the sidebar primitives that swaps the scrollable middle between a root rail and sub-panels (similar to the Vercel dashboard). Items with sub-navigation replace the whole middle region; they do not expand inline. Each sub-panel shows a back control and title.

Header and footer sit outside the scrollable VercelSidebarNav region. VercelSidebarNavProvider wraps the whole VercelSidebar so header widgets (for example, a command palette over the search field) can call setActivePanel.

The module re-exports every sidebar primitive from the registry sidebar item with a Vercel prefix (VercelSidebarProvider, VercelSidebar, VercelSidebarMenuButton, …) from @/components/ui/vercel-sidebar. It also exports filterVercelSidebarItems and VercelSidebarSearchPopover (Popover + Command) for global search across every nav entry you define.

Install

Configure your registry in components.json:

{
  "registries": {
    "@uxio": "https://ui.uxio.dev/r/styles/{style}/{name}.json"
  }
}

Then run:

npx shadcn@latest add @uxio/vercel-sidebar

This installs vercel-sidebar and bundles sidebar, command, and popover (and their dependencies). The Base UI or Radix implementation follows your components.json style.

Usage

Wrap VercelSidebar with VercelSidebarNavProvider (panel state + context). Structure: VercelSidebarHeaderVercelSidebarNavVercelSidebarFooter. Inside VercelSidebarNav, add one VercelSidebarPanel per view (e.g. panelId="root" and panelId="observability"). Use useVercelSidebarNav() (or buttons that call setActivePanel) to open a sub-panel from the root rail.

Build a VercelSidebarSearchItem[] list that mirrors every link (root and sub-panels). Pass it to VercelSidebarSearchPopover; selection calls setActivePanel(item.panelId) when the popover is under the same provider (or pass onSelect for full control). Use filterVercelSidebarItems(items, query) anywhere you need the same matching rules (e.g. inline filtering).

SSR and client-side navigation

Set defaultPanel on VercelSidebarNavProvider to match the current route so the first paint shows the correct panel (no flash of the root rail). For client-side navigations, use controlled panel and onPanelChange on the provider so the UI stays in sync with the URL.

import {
  VercelSidebar,
  VercelSidebarBack,
  VercelSidebarFooter,
  VercelSidebarHeader,
  VercelSidebarMenu,
  VercelSidebarMenuButton,
  VercelSidebarMenuItem,
  VercelSidebarNav,
  VercelSidebarNavProvider,
  VercelSidebarPanel,
  VercelSidebarProvider,
  useVercelSidebarNav,
} from "@/components/ui/vercel-sidebar"

function RootLinks() {
  const { setActivePanel } = useVercelSidebarNav()
  return (
    <VercelSidebarMenu>
      <VercelSidebarMenuItem>
        <VercelSidebarMenuButton type="button" onClick={() => setActivePanel("settings")}>
          Settings
        </VercelSidebarMenuButton>
      </VercelSidebarMenuItem>
    </VercelSidebarMenu>
  )
}

export function AppSidebar({ initialPanel = "root" }: { initialPanel?: string }) {
  return (
    <VercelSidebarProvider>
      <VercelSidebarNavProvider defaultPanel={initialPanel}>
        <VercelSidebar collapsible="none">
          <VercelSidebarHeader>
            {/* project switcher, VercelSidebarSearchPopover, … */}
          </VercelSidebarHeader>
          <VercelSidebarNav>
            <VercelSidebarPanel panelId="root">
              <RootLinks />
            </VercelSidebarPanel>
            <VercelSidebarPanel panelId="settings">
              <VercelSidebarBack title="Settings" />
              <VercelSidebarMenu>{/* sub-links */}</VercelSidebarMenu>
            </VercelSidebarPanel>
          </VercelSidebarNav>
          <VercelSidebarFooter>{/* user, actions */}</VercelSidebarFooter>
        </VercelSidebar>
      </VercelSidebarNavProvider>
    </VercelSidebarProvider>
  )
}

API

VercelSidebarNavProvider

Holds panel state and React context for VercelSidebarNav, VercelSidebarPanel, VercelSidebarBack, and VercelSidebarSearchPopover. Wrap your VercelSidebar (or at least everything that needs useVercelSidebarNav).

PropTypeDefaultDescription
defaultPanelstring"root"Initial panel when uncontrolled. Should match the server render for SSR.
rootPanelIdstring"root"Panel the Back control returns to when onBack is not passed.
panelstringControlled active panel. When set, use onPanelChange to update.
onPanelChange(id: string) => voidCalled when the active panel changes in controlled mode.

VercelSidebarNav

Renders VercelSidebarContent with the stacked panel region. Must be used inside VercelSidebarNavProvider. Props extend VercelSidebarContent.

VercelSidebarSearchPopover

Popover + Command: filtered list of VercelSidebarSearchItem. children must be a single element that accepts a ref (e.g. a button styled as the search row). With shouldFilter={false}, filtering uses filterVercelSidebarItems so subtitle and keywords match like the palette.

PropTypeDescription
itemsreadonly VercelSidebarSearchItem[]All searchable entries.
open / onOpenChangeOptional controlled open state.
onSelect(item) => voidIf set, replaces default setActivePanel(item.panelId) handling.

filterVercelSidebarItems / VercelSidebarSearchItem

Exported for reuse: same token rules as the command palette (trim, lowercase, every whitespace-separated word must appear in title, subtitle, or keywords).

useOptionalVercelSidebarNav()

Same shape as useVercelSidebarNav() or null if there is no provider (safe for optional UI).

VercelSidebarPanel

PropTypeDescription
panelIdstringUnique id for this view.
React.ComponentProps<"div">Inactive panels use the hidden attribute.

data-slot="vercel-sidebar-panel", data-panel, and data-state ("active" | "inactive") are set for styling.

VercelSidebarBack

PropTypeDescription
titleReact.ReactNodeSection title (e.g. “Observability”).
onBack() => voidOptional; defaults to setActivePanel(rootPanelId).
Omit<React.ComponentProps<"div">, "title">Row wrapper for the back control and title.

data-slot="vercel-sidebar-back". Full-width VercelSidebarMenuButton: chevron + panel title only (aria-label="Back"). Panels use slide and fade animations via Motion (motion).

useVercelSidebarNav()

Returns { activePanel, setActivePanel, rootPanelId }. Must be used under VercelSidebarNavProvider (e.g. inside VercelSidebarNav or the header when the provider wraps VercelSidebar).

Example

Click Observability to swap the middle content; use Back to return to the root rail.