suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
  • AI
GitHubCreate
DocsComponentsPatternsInteractionsChartsBlocksAICreate
Patterns19
  • Overview
  • Action Bar
  • Action Menu
  • App Shell
  • Command Palette
  • Confirm
  • Context Actions
  • Date Picker
  • Flyout
  • Modal
  • Navbar
  • Paged List
  • Panel
  • Picker
  • Preview
  • Search Picker
  • Segment Picker
  • Split View
  • Table List
  • Trail

suiss/ui · Pattern

App Shell

An application frame with a collapsible sidebar on desktop and a floating tab bar on mobile.

App Shell is an open-source pattern from suiss/ui for React. The @esuiss/ui CLI copies its source into your project, so you own the code.

import {
  CalendarBlankIcon,
  GearIcon,
  HouseIcon,
  TrayIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import {
  AppShell,
  type AppShellItem,
  AppShellTrigger,
} from "@/components/patterns/app-shell"

const items: AppShellItem[] = [
  {
    value: "home",

AppShell builds navigation from an items array of AppShellItem objects and wraps your page content. On desktop it renders a Sidebar inside a SidebarProvider, with optional header and footer, group labels, badges, tooltips on collapsed icons and a SidebarRail; the page renders in a SidebarInset.

Below the 48rem breakpoint the sidebar is replaced by a TabBar pinned to the bottom of the screen, and the content renders in a main element padded to clear it. Items that do not fit in the tab bar move behind its More trigger. Use it as the outermost layout of an app with a handful of top-level destinations.

Installation

CLI

$ pnpm dlx shadcn@latest add @esuiss/app-shell

Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add patterns app-shell

Manual

  1. Add the cn helper.

  2. Add the pieces it builds on: Sidebar, Tab Bar.

  3. Copy the source into components/patterns/app-shell.tsx:

components/patterns/app-shell.tsx
"use client"

import * as React from "react"
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuBadge,
  SidebarMenuButton,
  SidebarMenuItem,
  1. Update the import paths to match your project.

Usage

import { AppShell, AppShellTrigger } from "@/components/patterns/app-shell"
<ShellExample items={items} />

Each AppShellItem has a value, label and icon, plus optional href, badge, group and disabled. Items with the same group render under a shared SidebarGroupLabel. The selected item is controlled with value and onValueChange, or uncontrolled with defaultValue; it defaults to the first item. Items with an href render through render, which defaults to a plain anchor, so you can pass your router's link component.

The sidebar accepts variant (sidebar, floating or inset), collapsible (icon by default, offcanvas or none), side, rail, and open, defaultOpen and onOpenChange for its expanded state. moreLabel sets the tab bar's More trigger text and label names the navigation landmark (Main by default). Place AppShellTrigger in your page header to toggle the sidebar.

Examples

With header and footer

Adds a brand menu in the sidebar header and an account menu in the footer.

import {
  CalendarBlankIcon,
  GearIcon,
  HouseIcon,
  TrayIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import {
  AppShell,
  type AppShellItem,
  AppShellTrigger,
} from "@/components/patterns/app-shell"
import {
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,

Grouped

Splits items into labelled Platform and Workspace groups and disables one item.

import {
  CalendarBlankIcon,
  ChartBarIcon,
  FolderIcon,
  GearIcon,
  HouseIcon,
  LifebuoyIcon,
  TrayIcon,
  UsersIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import {
  AppShell,
  type AppShellItem,
  AppShellTrigger,
} from "@/components/patterns/app-shell"

More tab

Uses eight items with defaultValue so extra items overflow into the mobile tab bar's More panel.

import {
  CalendarBlankIcon,
  ChartBarIcon,
  FolderIcon,
  GearIcon,
  HouseIcon,
  LifebuoyIcon,
  TrayIcon,
  UsersIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import {
  AppShell,
  type AppShellItem,
  AppShellTrigger,
} from "@/components/patterns/app-shell"

Links

Gives every item an href so each renders as a link to its route.

import {
  CalendarBlankIcon,
  GearIcon,
  HouseIcon,
  TrayIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import {
  AppShell,
  type AppShellItem,
  AppShellTrigger,
} from "@/components/patterns/app-shell"

const items: AppShellItem[] = [
  {
    value: "home",

Floating

Uses the floating sidebar variant.

import {
  CalendarBlankIcon,
  GearIcon,
  HouseIcon,
  TrayIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import {
  AppShell,
  type AppShellItem,
  AppShellTrigger,
} from "@/components/patterns/app-shell"
import {
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,

Inset

Uses the inset sidebar variant.

import {
  CalendarBlankIcon,
  GearIcon,
  HouseIcon,
  TrayIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import {
  AppShell,
  type AppShellItem,
  AppShellTrigger,
} from "@/components/patterns/app-shell"
import {
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,

Offcanvas

Sets collapsible to offcanvas so collapsing slides the whole sidebar out of view.

import {
  CalendarBlankIcon,
  GearIcon,
  HouseIcon,
  TrayIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import {
  AppShell,
  type AppShellItem,
  AppShellTrigger,
} from "@/components/patterns/app-shell"
import {
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,

Right side

Places the sidebar on the right with side.

import {
  CalendarBlankIcon,
  GearIcon,
  HouseIcon,
  TrayIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import {
  AppShell,
  type AppShellItem,
  AppShellTrigger,
} from "@/components/patterns/app-shell"
import {
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,

Collapsed

Starts with the sidebar collapsed to icons through defaultOpen={false}.

import {
  CalendarBlankIcon,
  GearIcon,
  HouseIcon,
  TrayIcon,
} from "@phosphor-icons/react"
import * as React from "react"
import {
  AppShell,
  type AppShellItem,
  AppShellTrigger,
} from "@/components/patterns/app-shell"
import {
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,

API Reference

01

AppShell

The root layout that renders the sidebar or tab bar from items and wraps the page content.

PropTypeDefaultDescription
itemsAppShellItem[]RequiredThe destinations, each with a value, label and icon.
valuestring-The selected item, for controlled use.
defaultValuestringFirst itemThe initially selected item when uncontrolled.
onValueChange(value: string) => void-Called when an item is chosen.
headerReact.ReactNode-Content at the top of the sidebar.
footerReact.ReactNode-Content at the bottom of the sidebar.
variant"sidebar" | "floating" | "inset""sidebar"The sidebar style.
collapsible"offcanvas" | "icon" | "none""icon"How the sidebar collapses.
side"left" | "right""left"The side the sidebar sits on.
railbooleantrueShows a rail along the sidebar edge that toggles it.
openboolean-Whether the sidebar is expanded, for controlled use.
defaultOpenbooleantrueWhether the sidebar starts expanded when uncontrolled.
onOpenChange(open: boolean) => void-Called when the sidebar expands or collapses.
moreLabelstring"More"Text of the More tab on mobile.
renderReact.ReactElement<AppShellLink />The element items with an href render as, such as your router's link.

Also accepts the props of React.ComponentProps<typeof AppShellLayout>.

02

AppShellTrigger

A SidebarTrigger that toggles the sidebar on desktop and renders nothing on mobile.

Also accepts the props of React.ComponentProps<typeof SidebarTrigger>.

Accessibility

  • The selected item has aria-current="page".
  • Collapsed sidebar items show their label in a tooltip on hover.
  • AppShellTrigger is labelled Toggle Sidebar, and Cmd+B on macOS or Ctrl+B elsewhere toggles the sidebar.
  • With collapsible="offcanvas", the hidden sidebar is removed from view and the tab order.
  • Disabled items are disabled buttons.

Notes

The sidebar exposes data-state (expanded or collapsed), data-collapsible, data-variant and data-side for styling. On mobile the wrapper carries data-slot="app-shell" and the content data-slot="app-shell-content"; className applies to the content area on both layouts. rail only renders when collapsible is not none.

NextCommand PalettePreviousAction Menu

On this page

  1. 01Installation
    • CLI
    • Manual
  2. 02Usage
  3. 03Examples
    • With header and footer
    • Grouped
    • More tab
    • Links
    • Floating
    • Inset
    • Offcanvas
    • Right side
    • Collapsed
  4. 04API Reference
    • AppShell
    • AppShellTrigger
  5. 05Accessibility
  6. 06Notes