# Admin Shell

> A React admin shell layout with a workspace sidebar, a top bar with search and notifications, a command menu and a bottom tab bar on phones.

- Type: React block (Blocks)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.1.0
- Install: `npx wingo-ui@latest add admin-shell`
- Docs: https://wingo-ui.com/components/admin-shell

## Interaction

The sidebar lists the navigation in groups with counts; the active page carries a pill and aria-current, and a record route such as /clients/northwind-tech keeps its section active. The workspace card at the top opens the switcher: picking another workspace spins its row for a moment, switches and confirms with a toast. The account card at the bottom opens the user menu (profile, settings, billing, help, the theme and sign out). Press mod+b or the toggle in the sidebar footer to collapse it to an icon rail with tooltips and flyouts, drag its edge to resize it (double click resets) or use the arrow keys on the handle. The top bar shows the page title or a breadcrumb, the search field (press / to focus it; results for clients, contacts, deals and invoices appear as you type, arrows and Enter open one), the notification bell (tabs for all, unread and mentions, mark as read, swipe to dismiss) and the theme toggle. mod+k opens the command menu: go to any page, create a client, invoice or deal, invite a colleague, switch the theme or toggle the sidebar. Below 1024px the sidebar becomes a drawer behind the menu button, the top bar turns compact with a centered title, a search icon that opens a full screen search, the bell (a sheet) and your avatar, and a five tab bar sits on the bottom edge above the safe area; its More tab opens the drawer and the top bar slides away while you scroll down. The skip link (Skip to content) is the first stop for keyboard users. design="rail" keeps the sidebar on its icon rail (hover or focus an icon for its name); design="topbar" moves the nav into the top bar as tabs, where the arrow keys move between tabs and the More menu holds the ones that do not fit.

## Installation

```bash
npx wingo-ui@latest add admin-shell
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/admin-shell` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/blocks/admin-shell.tsx`
- `components/blocks/admin-shell-data.ts`

npm dependencies: `npm i lucide-react`

Wingo UI dependencies (installed automatically):

- [App Shell](https://wingo-ui.com/components/app-shell) ([Markdown](https://wingo-ui.com/components/app-shell.md))
- [Badge](https://wingo-ui.com/components/badge) ([Markdown](https://wingo-ui.com/components/badge.md))
- [Bottom Tab Bar](https://wingo-ui.com/components/bottom-tab-bar) ([Markdown](https://wingo-ui.com/components/bottom-tab-bar.md))
- [Breadcrumb](https://wingo-ui.com/components/breadcrumb) ([Markdown](https://wingo-ui.com/components/breadcrumb.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Command Menu](https://wingo-ui.com/components/command-menu) ([Markdown](https://wingo-ui.com/components/command-menu.md))
- [Dropdown Menu](https://wingo-ui.com/components/dropdown-menu) ([Markdown](https://wingo-ui.com/components/dropdown-menu.md))
- [Global Search](https://wingo-ui.com/components/global-search) ([Markdown](https://wingo-ui.com/components/global-search.md))
- [Notification Center](https://wingo-ui.com/components/notification-center) ([Markdown](https://wingo-ui.com/components/notification-center.md))
- [Sidebar](https://wingo-ui.com/components/sidebar) ([Markdown](https://wingo-ui.com/components/sidebar.md))
- [Theme Toggle](https://wingo-ui.com/components/theme-toggle) ([Markdown](https://wingo-ui.com/components/theme-toggle.md))
- [Toast](https://wingo-ui.com/components/toast) ([Markdown](https://wingo-ui.com/components/toast.md))
- [Topbar](https://wingo-ui.com/components/topbar) ([Markdown](https://wingo-ui.com/components/topbar.md))
- [User Menu](https://wingo-ui.com/components/user-menu) ([Markdown](https://wingo-ui.com/components/user-menu.md))
- [Workspace Switcher](https://wingo-ui.com/components/workspace-switcher) ([Markdown](https://wingo-ui.com/components/workspace-switcher.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [Commerce](https://wingo-ui.com/components/commerce) ([Markdown](https://wingo-ui.com/components/commerce.md))
- [Form validation](https://wingo-ui.com/components/form-validation) ([Markdown](https://wingo-ui.com/components/form-validation.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
// app/(admin)/layout.tsx
"use client"

import Link from "next/link"
import { usePathname, useRouter } from "next/navigation"
import type { ReactNode } from "react"
import { AdminShell } from "@/components/blocks/admin-shell"

export default function AdminLayout({ children }: { children: ReactNode }) {
  const pathname = usePathname()
  const router = useRouter()
  return (
    <AdminShell currentPath={pathname} linkComponent={Link} onNavigate={router.push}>
      {children}
    </AdminShell>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children?` | `ReactNode` |  | The page, rendered once inside main (never twice for phone and desktop). |
| `nav?` | `SidebarNavGroup[]` |  | The sidebar groups (ADMIN_SHELL_SAMPLE.nav: Home, AI assistant; Sales with Clients 128, Contacts, Deals 12, Orders; Finance with Invoices 3 overdue, Reports; Operations; Settings and Help). Also feeds the command menu's Go to group. |
| `tabs?` | `BottomTabBarItem[]` |  | The phone tab bar, 3 to 5 items (ADMIN_SHELL_SAMPLE.tabs: Home, Clients, Deals, Invoices, More). An item with id "more" and no href opens the drawer. [] hides the bar. |
| `currentPath?` | `string` | `"/"` | The active route (next: usePathname()). Selects the nav item, the tab and the default title; nested routes match by prefix. |
| `title?` | `ReactNode` | `""` | The top bar title. Empty uses the active nav item's label, or labels.appName when no item matches. |
| `breadcrumb?` | `BreadcrumbEntry[]` |  | Replaces the title with a breadcrumb (record pages: Clients / Northwind Tech Inc.). Phones show the last entry as the title. |
| `actions?` | `ReactNode` |  | Extra top bar actions before the notifications, such as a New button. |
| `banner?` | `ReactNode` |  | A strip above everything, usually a Banner (trial ending, maintenance). Hidden when printing. |
| `workspaces?` | `Workspace[]` |  | The switcher's list (ADMIN_SHELL_SAMPLE.workspaces: Summit Software Inc., Summit Logistics, Personal). One workspace hides the chevron; none shows the logo instead. |
| `workspace?` | `string` |  | The active workspace id, controlled. Pair it with onWorkspaceChange. |
| `defaultWorkspace?` | `string` | `"summit-software"` | The workspace selected at first, uncontrolled. |
| `onWorkspaceChange?` | `(id: string, workspace: Workspace) => (void \| Promise<unknown>)` |  | Runs when a workspace is picked. A returned promise keeps the row spinning; once it resolves the switch happens and a toast confirms it, a rejection keeps the old workspace and shows an error toast. Without it a 700 ms request is simulated. |
| `user?` | `{ name: string; email: string; avatar?: string; plan?: string } \| null` |  | The signed-in person for the sidebar card and the phone avatar menu (ADMIN_SHELL_SAMPLE.user: Anna Price, Administrator). null shows a sign-in button. |
| `userMenuItems?` | `MenuEntry[]` |  | The account menu entries above sign out. Default: Profile, Settings, Billing, Help and a separator, navigating through the router. |
| `onSignOut?` | `() => (void \| Promise<unknown>)` |  | The sign-out row; a promise keeps the menu open with a spinner and a rejection shows an error toast. Default: a toast "You signed out". |
| `signInHref?` | `string` | `"/sign-in"` | Where the sign-in button points while there is no user. |
| `notifications?` | `NotificationItem[]` |  | The bell's inbox (ADMIN_SHELL_SAMPLE.notifications: an overdue invoice, a mention, a payment, a won deal, an import and an accepted invite; 3 unread). Read, unread and dismiss are handled by the notification center. |
| `onNotificationSelect?` | `(item: NotificationItem) => void` |  | Opening a notification. Default: it turns read and navigates to its href through the router. |
| `search?` | `boolean` | `true` | The global search in the top bar (an icon that opens a full screen search on phones). |
| `searchResults?` | `SearchResultGroup[]` |  | Static results filtered as you type (ADMIN_SHELL_SAMPLE.searchResults: clients, contacts, deals and invoices, grouped). |
| `loadSearch?` | `(query: string, context: { signal: AbortSignal; scope: string }) => Promise<SearchResultGroup[]>` |  | Server search instead of searchResults: debounced, the previous request aborted. |
| `searchSuggestions?` | `SearchResult[]` |  | Quick links while the search is empty (Overdue invoices, Deals in negotiation, Clients in Chicago). |
| `searchShortcut?` | `string` | `"/"` | Focuses the search (opens it on phones). "" binds nothing. |
| `commandMenu?` | `boolean` | `true` | The command palette: every nav page, the actions (client, invoice, deal, invite), the theme and the sidebar toggle. |
| `commands?` | `CommandGroup[]` |  | The palette's root groups. Default: Go to (built from nav) and Actions (ADMIN_SHELL_SAMPLE.commands). The Appearance group is always added. |
| `commandShortcut?` | `string` | `"mod+k"` | Opens the command menu. "" binds nothing. |
| `sidebarShortcut?` | `string` | `"mod+b"` | Toggles the sidebar: the rail on desktop (classic design), the drawer on phones. "" binds nothing. |
| `design?` | `"classic" \| "rail" \| "topbar"` | `"classic"` | classic: the sidebar with workspaces and the account card. rail: a fixed 64px icon rail with tooltips and flyouts, the workspace logo on top and the avatar at the bottom. topbar: no sidebar on desktop; the logo, the nav as tabs (what does not fit goes under More), search, notifications and the avatar share the top bar. Phones keep the drawer and the tab bar in every design. |
| `layout?` | `"page" \| "fill"` | `"page"` | page: the window scrolls under a sticky top bar. fill: the shell is exactly the viewport, the top bar stays put and main is a flex column for boards and inboxes that scroll inside. |
| `variant?` | `"sidebar" \| "inset" \| "floating"` | `"inset"` | inset: the page is a raised panel beside the sidebar. sidebar: flush columns with a hairline. floating: the sidebar is a raised panel off the edges. Applies to the classic design. |
| `defaultCollapsed?` | `boolean` | `false` | Starts as the icon rail on desktop. Applies to the classic design. |
| `collapsible?` | `"icon" \| "offcanvas" \| "none"` | `"icon"` | icon: collapses to a 64px rail. offcanvas: slides out completely. none: always expanded. Applies to the classic design. |
| `persistKey?` | `string` | `"admin-shell"` | Remembers collapsed and width in cookies (readable by a server layout) and the search and palette recents. "" turns it off; the demo passes "". |
| `resizable?` | `boolean` | `true` | Drag the sidebar edge between 200 and 400px; below 120px it snaps to the rail. Applies to the classic design. |
| `mobileBreakpoint?` | `"md" \| "lg"` | `"lg"` | Below it: the drawer, the compact top bar and the tab bar. lg gives an 820px tablet the full width for the page. |
| `sidebarActiveStyle?` | `"soft" \| "raised" \| "bar"` | `"soft"` | How the sidebar marks the active page: a soft pill, a raised pill or a bar. |
| `hideTabBar?` | `boolean` | `false` | Hides the phone tab bar (pages with their own bottom bar, thumbnails). |
| `logo?` | `ReactNode` |  | Shown in the sidebar header when there are no workspaces. Default: a 28px tile with an "S" and Summit CRM. |
| `locale?` | `string` | `"en-US"` | Intl locale for counts and times in the search and the notifications. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The accent of the active nav item, the active tab and the unread dots. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` |  | The router link every nav link renders through (next/link). Default: the LinkProvider's, else <a>. |
| `onNavigate?` | `(href: string) => void` |  | The router's push for search results, commands and menu items picked without an anchor (next: useRouter().push). |
| `labels?` | `Partial<AdminShellLabels>` |  | Every visible string, English by default (DEFAULT_ADMIN_SHELL_LABELS; ADMIN_SHELL_LABELS_EN is the same set). Nested sets translate the composed items. |
| `classNames?` | `Partial<Record<"sidebar" \| "topbar" \| "main" \| "tabs", string>>` |  | Extra classes for the sidebar, the top bar, main and the tab bar row. |
| `className?` | `string` |  | Extra classes for the root. Other props spread onto it. |

## Parts

### ADMIN_SHELL_DESIGNS (constant)

The design names, classic first: ["classic", "rail", "topbar"].

### AdminShellDesign (type)

The layout design: "classic" | "rail" | "topbar".

### DEFAULT_ADMIN_SHELL_LABELS (constant)

The English strings for every visible label, the default of labels. Type: AdminShellLabels.

### ADMIN_SHELL_LABELS_EN (constant)

The same English strings under their older name, kept for existing imports.

### ADMIN_SHELL_SAMPLE (constant)

The Summit CRM demo data: nav, tabs, workspaces, user, notifications, searchResults, searchSuggestions and commands.

### ADMIN_SHELL_NOW (constant)

The demo clock (Sep 24, 2026, 4:40 PM) as a timestamp, so notification times stay stable.

### AdminShellProps (type)

Props of AdminShell, including the div attributes of the root.

### AdminShellUser (type)

The signed-in person: { name, email, avatar?, plan? }.

## Examples

- **Rail design**: design="rail": a fixed 64px icon rail with tooltips, the workspace logo on top and your avatar at the bottom; phones keep the drawer.
- **Topbar design**: design="topbar": no sidebar on desktop; the logo, the nav as tabs (the rest under More), search, notifications and your avatar share the top bar.
- **Collapsed rail with a trial banner**: defaultCollapsed starts on the icon rail (tooltips and flyouts); banner sits above everything.
- **A record route with a breadcrumb**: currentPath="/clients/northwind-tech" keeps Clients active by prefix; breadcrumb replaces the title.
- **Fill layout with a board**: layout="fill": the shell is the viewport and the pipeline scrolls sideways inside the page.
- **Floating sidebar, brand tone**: variant="floating", tone="brand" and the raised active pill.
- **en-GB with a custom user menu**: locale="en-GB" and userMenuItems replacing the default Profile, Settings, Billing and Help entries.
- **Signed out, no workspaces**: user={null} shows a sign-in button in the sidebar; no workspaces shows the logo.

## See also

- [App Shell](https://wingo-ui.com/components/app-shell) ([Markdown](https://wingo-ui.com/components/app-shell.md))
- [Sidebar](https://wingo-ui.com/components/sidebar) ([Markdown](https://wingo-ui.com/components/sidebar.md))
- [Topbar](https://wingo-ui.com/components/topbar) ([Markdown](https://wingo-ui.com/components/topbar.md))
- [KPI Dashboard](https://wingo-ui.com/components/kpi-dashboard) ([Markdown](https://wingo-ui.com/components/kpi-dashboard.md))

---

Canonical: https://wingo-ui.com/components/admin-shell
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
