ComponentsPage layouts

App shell

The one shell every app runs on — CMS, CRM, HR, PRM — with a different app definition. Sidebar, top bar, content, skip link, mobile sheet nav, a ⌘K palette with “Go to” commands derived from every app's nav, toasts and confirm. Router-agnostic.

Sidebar, Top bar, Command, Sheet, Toast components/app-shell.tsx, components/sidebar.tsx, components/top-bar.tsx, components/command.tsx, lib/nav.ts, styles/navigation.csstheme · material · accent · density · font
Skip to content

Northwind

Dashboard

Visitors · 30 days

28,412+8.4%

Published posts

144

Form submissions

312

Recent posts

/site
  • A field guide to GDPR consent bannersNov 9
  • Writing alt text that actually helpsNov 12
  • Local SEO checklist for 2026Nov 6

Installation

$ pnpm add @vamos-ui/react

The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";

Usage

"use client";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { AppShellProvider, AppShell, NotificationCenter, type AppDefinition } from "@vamos-ui/react";

const site: AppDefinition = { id: "site", name: "Site", href: "/site", icon: Globe, nav: siteNav, links: [{ id: "view", label: "View site", href: "https://example.com" }] };
const crm: AppDefinition = { id: "crm", name: "CRM", href: "/crm", icon: Handshake, nav: crmNav, accent: "ocean" };

export function Shell({ defaultCollapsed, children }) {
  const router = useRouter();
  return (
    <AppShellProvider apps={[site, crm]} sharedNav={shared} linkComponent={Link} currentPath={usePathname()} navigate={router.push} defaultCollapsed={defaultCollapsed}>
      <AppShell user={{ name: "Ada Lovelace", email: "ada@northwind.io", items: account }} notifications={<NotificationCenter adapter={inbox} />}>
        {children}
      </AppShell>
    </AppShellProvider>
  );
}

Landmarks: banner (top bar), navigation (sidebar), main (skip-link target, focused after route changes). The skip link is the first focusable element.

App definitions contain components and functions, so keep them in a client module; compute defaultCollapsed and activeApp in the server layout to avoid a hydration flash.

Glass: the shell owns the two permanent blurs (sidebar, top bar); flyouts and nested layers never blur.

Examples

Multi-app

Switching apps swaps the nav, commands, “New ▾” menu, app links and accent. Top bar, user menu, notifications and ⌘K stay. Apps a user can't open (permission) never appear.

Skip to content

CRM

Customers & sales

Open deals

37

Pipeline value

$412,800

Won this month

$68,450+12%

Deals closing soon

/crm/pipeline
  • Fjord Freight · Fleet trackingDec 15
  • Harbor & Pine Legal · RetainerDec 18
  • Brightline Studio · Site rebuildJan 9

Workspaces

A workspace/tenant switcher shares the app switcher's panel for multi-company setups.

Skip to content

Defne Spa

Posts

Content for Defne Spa.

Visitors · 30 days

28,412+8.4%

Published posts

144

Form submissions

312

Recent posts

/site/posts
  • A field guide to GDPR consent bannersNov 9
  • Writing alt text that actually helpsNov 12
  • Local SEO checklist for 2026Nov 6

Mobile

The shell responds to its own width: below 768px the sidebar moves into a start-side sheet with accordion levels; 768–1279px defaults to the rail when the user has no preference.

Skip to content

Categories

Visitors · 30 days

28,412+8.4%

Published posts

144

Form submissions

312

Recent posts

/site/posts/categories
  • A field guide to GDPR consent bannersNov 9
  • Writing alt text that actually helpsNov 12
  • Local SEO checklist for 2026Nov 6

Next.js router

Pass Link, usePathname() and router.push. Read the ui-sidebar cookie in the server layout with parseSidebarCookie and pass defaultCollapsed to avoid a flash. These links navigate this site.

Skip to content

Routing

The shell reads the route from Next and navigates through it — nothing else to wire.

linkComponent
next/linkSidebar, breadcrumb and app switcher links
currentPath
/docs/components/app-shell/Active item and automatic breadcrumb
navigate
router.pushCommand palette and keyboard shortcuts

Top navigation (portal)

layout="topnav" puts top-level links in the top bar and drops the sidebar — for client portals.

Skip to content

Documents

Contracts, briefs and reports shared with you.

Open invoices

2

Shared documents

18

Unread messages

3

Recently shared

/portal/documents
  • Brand guidelines v3.pdfNov 8
  • Q4 campaign brief.docxNov 4
  • Service agreement 2027.pdfOct 29

API

AppShellProvider

PropTypeDefault
apps
Every app on the shell. Filtered by permission.
AppDefinition[]—
activeApp
Default: the app whose base/href is the longest prefix of currentPath, else the last one used.
string—
sharedNav
Bottom menu shared by every app (users, settings).
NavNode[]—
commands
Commands available in every app.
CommandAction[]—
linkComponent / currentPath / navigate / can
Router and permission pieces; omit when a VamosProvider above sets them.
VamosProvider bridge—
features
Feature flags for nav items.
Record<string, boolean>—
defaultCollapsed
Rail preference from the ui-sidebar cookie; undefined = follow the width.
boolean—
onCollapsedChange
Persist the preference elsewhere.
(v: boolean) => voidwrites the cookie
workspaces / activeWorkspace / onWorkspaceChange
Tenant switcher.
…—
shortcuts
mod+k (palette) and mod+\ (rail). Off for embedded shells.
booleantrue
locale / messages / dir
Passed to the LocaleProvider.
…—

AppShell

PropTypeDefault
user
User menu in the default top bar.
UserMenuProps—
notifications
<NotificationCenter adapter={…} /> slot.
ReactNode—
actions
Extra top-bar actions (theme, help).
ReactNode—
badges
Live sidebar badges by nav id.
Record<string, BadgeSpec>—
banner
Slot above the top bar (offline, trial).
ReactNode—
sidebar / topbar
Replace or remove the defaults.
ReactNode | false—
sidebarFooter / appSwitcherFooter
Extra sidebar content.
ReactNode—
layout
Portal-style top navigation.
"sidebar" | "topnav""sidebar"
loading
Sidebar skeleton while permissions load.
booleanfalse
announceRouteChanges
Focus main and announce the page title after navigation.
booleantrue
height
fill: fill the parent (embedded previews).
"screen" | "fill""screen"
globals
Mount the ⌘K palette, Toaster, ConfirmProvider and TooltipProvider.
booleantrue

AppDefinition

PropTypeDefault
id / name / description / icon
Shown in the app switcher and as the ⌘K app badge.
string / ReactNode—
href / base
App home and its path prefix. An absolute href does a full navigation (multi-zone).
string—
accent
data-accent while active (CRM = ocean).
string—
nav
The app's sidebar; also ⌘K “Go to”.
NavNode[]—
commands / sources
⌘K commands (active app) and record search (always).
CommandAction[] / CommandSource[]—
newMenu
Top bar “New ▾”.
MenuEntry[]—
links
Top-bar links for this app only — “View site” in the Site app.
{ id, label, href, icon?, external? }[]—
permission
Hide the app entirely.
string—

useAppShell()

PropTypeDefault
activeApp / apps / nav / sharedNav
Current state (nav already filtered).
…—
collapsed / setCollapsed
Rail.
boolean / (v) => void—
openMobileNav / closeMobileNav / mobileNavOpen
Mobile sheet.
…—
trail
Active nav trail (Breadcrumb, Page header).
string[]—
switchApp(id)
Navigate to another app.
void—