designComponents

Search the design system…

Search the design system…

layouts

AppShell

Universal in-app layout — TopBar with brand + breadcrumbs, collapsible Sidebar with three modes (expanded / collapsed / hover), scrollable content area. The chrome around every product page.

The sidebar mode is persisted across reloads via a small client-side store. Nav structure is supplied via `navGroups` so each product wires its own sections. AppShell already wraps everything in `TooltipProvider` — don't add another one inside. Two opt-out props for special cases: `hideBrand` suppresses the wordmark in the topbar (use when the product's identity is carried entirely by another piece of chrome, e.g. an OrgSwitcher with workspace avatar); `hideSidebar` removes the left rail entirely (`navGroups` becomes optional in that mode) — useful for routes that want the global topbar but no left nav, e.g. a `/settings` family when the team prefers familiar chrome over strict focused-mode.

Install

Pull this component (and its dependencies) straight into your app via the shadcn CLI:

npx shadcn@latest add https://design.oapps.io/r/app-shell.json

Or import from the workspace package:

import { AppShell, NavItem, NavGroup } from "@8maverik8/design";

Examples

Schematic preview

AppShell occupies the full viewport, so the live one can't honestly fit inside a doc page. The mock below mirrors the exact structure (TopBar + Sidebar + main + bottom mode-picker). Mount the real one as the root layout of your Next.js app.

cip
/Audiences

Workspace

Dashboard
Audiences
Campaigns

Data

Persons
Workspaces
⇆ Sidebar
Audiences
import { AppShell, type NavGroup } from "@8maverik8/design";
import { Home, Users, Megaphone } from "lucide-react";

const navGroups: NavGroup[] = [
  {
    section: "Workspace",
    items: [
      { label: "Dashboard", icon: Home, href: "/" },
      { label: "Audiences", icon: Users, href: "/audiences" },
      { label: "Campaigns", icon: Megaphone, href: "/campaigns" },
    ],
  },
];

export default function RootLayout({ children }) {
  return (
    <AppShell wordmark="cip" navGroups={navGroups}>
      {children}
    </AppShell>
  );
}

Variants

Chrome mode

  • full(default)Default. Brand + topbar slots + collapsible left sidebar + scrollable main. The shape of every standard product page.
  • hideBrandDrop the wordmark + leading separator. Use when another topbar slot (typically `<OrgSwitcher>`) carries the product identity.
  • hideSidebarDrop the entire left rail; topbar persists. Use when a route family doesn't fit a left-nav model but you still want global topbar consistency (sign-out, brand, search). Alternative to `<SettingsShell>` for teams that prefer not to drop global chrome entirely.

Anatomy

  • wordmarkLowercase product name shown in the topbar (e.g. `"cip"`). Hidden when `hideBrand={true}`.
  • hideBrandSuppress the wordmark + the leading separator in the topbar. The first left slot collapses against the left edge. Use when another piece of chrome (e.g. `<OrgSwitcher>` with workspace avatar) already carries the product's identity.
  • hideSidebarRemove the left rail entirely. The topbar still renders (brand + slots + UserMenu). `navGroups` becomes optional in this mode. Use for routes that want consistent global chrome but no left nav (e.g. `/settings` family when the team prefers familiar chrome over `<SettingsShell>`'s strict focused-mode).
  • navGroupsArray of `{ section, items[] }`. Sections render as mono-caps headings; items are `{ label, icon, href }`. Required unless `hideSidebar={true}`.
  • topbarLeftSlot to the right of the brand — typically a `<BreadcrumbTrail>`.
  • topbarCenterCenter slot — typically the `<SearchPalette>` trigger button.
  • topbarRightRight slot — typically `<FeedbackButton>` + `<UserMenu>`.

Guidelines

  • Wire the `navGroups` once at the app's root layout — don't switch them per route.The sidebar should feel constant across the product. If a section needs to disappear contextually, hide it inside the page, not by mutating navGroups.
  • Pick exactly ONE pattern for `/settings`: either `<AppShell hideSidebar>` (keep familiar chrome, drop left nav) OR `<SettingsShell>` (strict focused-mode, no global chrome at all). Don't mix — the user must feel a consistent transition into settings across products.
  • Render your own page padding around children.AppShell's `<main>` already has `p-6`. DSPageShell's footer relies on this exact gutter to bleed correctly.
  • Pass `hideSidebar={true}` AND `navGroups`.When `hideSidebar` is true, `navGroups` is ignored — passing it is dead code that drifts away from reality. Drop the prop.