mdxui

Layout

App layout composition patterns and components

Layout

The mdxui layout system provides a complete set of components for building authenticated SaaS applications. This guide covers layout composition patterns, from the root App wrapper through Shell, Sidebar, Header, and content areas.

Layout Architecture

The layout system follows a hierarchical composition pattern:

App (Authentication & Providers)
└── Shell (Layout Structure)
    ├── Sidebar (Navigation)
    ├── Header (Top Bar)
    └── Main (Content Area)
        └── View (Dashboard, Settings, etc.)

Core Layout Components

App Wrapper

The App component is the root container providing authentication, theme, and global context.

interface AppProps {
  /** App display name */
  name: string
  /** Authentication configuration */
  auth?: {
    provider: 'workos' | 'clerk' | 'auth0' | 'custom'
    clientId?: string
    loginUrl?: string
    redirectUri?: string
  }
  /** Child content */
  children: React.ReactNode
}

Usage:

<App name="Acme Dashboard" auth={{ provider: 'workos' }}>
  <Shell>
    <Dashboard />
  </Shell>
</App>

See App component documentation for details.

Shell Component

The Shell provides the structural layout with sidebar and header positioning.

interface ShellProps {
  /** Shell children (Views) */
  children: React.ReactNode
  /** Sidebar configuration */
  sidebar?: {
    visible: boolean
    collapsed: boolean
    collapsedWidth: number
    width: number
    position: 'left' | 'right'
    showToggle: boolean
    persistState: boolean
  }
  /** Header configuration */
  header?: {
    visible: boolean
    height: number
    sticky: boolean
    bordered: boolean
  }
  /** Theme mode */
  theme?: 'light' | 'dark' | 'system'
  /** Loading state */
  loading?: boolean
  /** Shell variant */
  variant?: 'default' | 'compact' | 'minimal'
}

Usage:

<Shell>
  <Dashboard />
</Shell>

See Shell component documentation for details.

Layout Modes

The Shell supports different layout configurations for various use cases.

Standard Mode (Default)

Full sidebar with header:

┌─────────────────────────────────────┐
│            Header                   │
├──────────┬──────────────────────────┤
│          │                          │
│ Sidebar  │         Main             │
│          │                          │
│          │                          │
└──────────┴──────────────────────────┘
<Shell>
  <Dashboard />
</Shell>

Hide header for vertical focus:

┌──────────┬──────────────────────────┐
│          │                          │
│ Sidebar  │         Main             │
│          │                          │
│          │                          │
└──────────┴──────────────────────────┘
<Shell
  header={{
    visible: false
  }}
>
  <Dashboard />
</Shell>

Header-Only Mode

Hide sidebar for horizontal focus:

┌─────────────────────────────────────┐
│            Header                   │
├─────────────────────────────────────┤
│                                     │
│             Main                    │
│                                     │
│                                     │
└─────────────────────────────────────┘
<Shell
  sidebar={{
    visible: false
  }}
>
  <Dashboard />
</Shell>

Full-Screen Mode

Hide both for immersive experiences:

┌─────────────────────────────────────┐
│                                     │
│             Main                    │
│                                     │
│                                     │
└─────────────────────────────────────┘
<Shell
  sidebar={{ visible: false }}
  header={{ visible: false }}
>
  <EditorView />
</Shell>

Compact Mode

Collapsed sidebar by default:

┌─────────────────────────────────────┐
│            Header                   │
├───┬─────────────────────────────────┤
│ S │                                 │
│ i │          Main                   │
│ d │                                 │
│ e │                                 │
└───┴─────────────────────────────────┘
<Shell
  sidebar={{
    collapsed: true
  }}
>
  <Dashboard />
</Shell>

The Sidebar provides primary navigation with collapsible sections and user menu.

interface SidebarProps {
  /** Navigation items */
  items: ShellNavItem[]
  /** Footer items */
  footerItems?: ShellNavItem[]
  /** Is collapsed */
  collapsed?: boolean
  /** Collapse handler */
  onCollapse?: () => void
  /** Logo/branding */
  logo?: {
    src: string
    collapsedSrc?: string
    alt: string
    href?: string
  }
  /** User info */
  user?: UserContext
  /** Show workspace switcher */
  showWorkspaceSwitcher?: boolean
  /** Show search */
  showSearch?: boolean
}

See Sidebar component documentation for details.

Flat Navigation

Simple list of top-level items:

const navigation = [
  { id: 'dashboard', label: 'Dashboard', href: '/', icon: 'LayoutDashboard' },
  { id: 'projects', label: 'Projects', href: '/projects', icon: 'Folder' },
  { id: 'team', label: 'Team', href: '/team', icon: 'Users' },
  { id: 'settings', label: 'Settings', href: '/settings', icon: 'Settings' },
]

<Sidebar items={navigation} />

Grouped Navigation

Items organized into collapsible sections:

const navigation = [
  { id: 'dashboard', label: 'Dashboard', href: '/', icon: 'LayoutDashboard' },
  {
    id: 'developer',
    label: 'Developer',
    href: '/developer',
    icon: 'Code',
    children: [
      { id: 'api-keys', label: 'API Keys', href: '/keys', icon: 'Key' },
      { id: 'webhooks', label: 'Webhooks', href: '/webhooks', icon: 'Webhook' },
      { id: 'logs', label: 'Logs', href: '/logs', icon: 'FileText' },
    ]
  },
  { id: 'settings', label: 'Settings', href: '/settings', icon: 'Settings' },
]

<Sidebar items={navigation} />

Multi-Level Navigation

Deep hierarchy with sections:

const navigation = [
  {
    id: 'workspace',
    label: 'Workspace',
    type: 'heading'
  },
  { id: 'dashboard', label: 'Dashboard', href: '/', icon: 'LayoutDashboard' },
  { id: 'projects', label: 'Projects', href: '/projects', icon: 'Folder' },
  {
    id: 'separator-1',
    type: 'separator'
  },
  {
    id: 'developer',
    label: 'Developer',
    type: 'heading'
  },
  { id: 'api-keys', label: 'API Keys', href: '/keys', icon: 'Key' },
  { id: 'webhooks', label: 'Webhooks', href: '/webhooks', icon: 'Webhook' },
]

<Sidebar items={navigation} />

Header Component

The Header provides the top bar with breadcrumbs, search, notifications, and user menu.

interface HeaderProps {
  /** Title/page name */
  title?: string
  /** Subtitle/description */
  subtitle?: string
  /** Navigation items */
  nav?: ShellNavItem[]
  /** Show breadcrumbs */
  showBreadcrumbs?: boolean
  /** Breadcrumb items */
  breadcrumbs?: Array<{
    label: string
    href?: string
  }>
  /** Show search */
  showSearch?: boolean
  /** Show notifications */
  showNotifications?: boolean
  /** Notification count */
  notificationCount?: number
  /** User context */
  user?: UserContext
  /** Show user menu */
  showUserMenu?: boolean
  /** Is sticky */
  sticky?: boolean
  /** Show border */
  bordered?: boolean
}

See Header component documentation for details.

Header Patterns

Standard Header

Full header with all features:

<Header
  breadcrumbs={[
    { label: 'Dashboard', href: '/' },
    { label: 'Projects', href: '/projects' },
    { label: 'Acme Website' },
  ]}
  showSearch={true}
  showNotifications={true}
  notificationCount={3}
  user={currentUser}
/>

Minimal Header

Focus on current location:

<Header
  title="Dashboard"
  subtitle="Welcome back"
  user={currentUser}
  showSearch={false}
  showNotifications={false}
/>

Context Header

Page-specific actions:

<Header
  breadcrumbs={breadcrumbs}
  user={currentUser}
  right={
    <>
      <Button variant="outline">Export</Button>
      <Button>Create Project</Button>
    </>
  }
/>

The Breadcrumbs component shows hierarchical location context.

interface BreadcrumbsProps {
  /** Breadcrumb items */
  items: Array<{
    label: string
    href?: string
    icon?: string
    current?: boolean
  }>
  /** Separator character */
  separator?: string
  /** Show home icon */
  showHome?: boolean
  /** Home href */
  homeHref?: string
  /** Max items before collapse */
  maxItems?: number
}

Usage:

<Breadcrumbs
  items={[
    { label: 'Dashboard', href: '/' },
    { label: 'Projects', href: '/projects' },
    { label: 'Acme Website', current: true },
  ]}
/>

Renders: Dashboard > Projects > Acme Website

Dynamic Breadcrumbs

Generate breadcrumbs from the current route:

function useBreadcrumbs() {
  const pathname = usePathname()
  const segments = pathname.split('/').filter(Boolean)

  const breadcrumbs = [
    { label: 'Dashboard', href: '/' }
  ]

  let path = ''
  segments.forEach((segment, index) => {
    path += `/${segment}`
    const isLast = index === segments.length - 1
    breadcrumbs.push({
      label: formatSegment(segment),
      href: isLast ? undefined : path,
      current: isLast,
    })
  })

  return breadcrumbs
}

<Header breadcrumbs={useBreadcrumbs()} />

Layout Composition Patterns

Developer App Layout

API-focused application:

<App name="Developer Portal" auth={{ provider: 'workos' }}>
  <Shell>
    <Sidebar
      items={[
        { label: 'Overview', href: '/', icon: 'LayoutDashboard' },
        { label: 'API Keys', href: '/keys', icon: 'Key' },
        { label: 'Webhooks', href: '/webhooks', icon: 'Webhook' },
        { label: 'Usage', href: '/usage', icon: 'BarChart' },
        { label: 'Logs', href: '/logs', icon: 'FileText' },
        { label: 'Docs', href: '/docs', icon: 'Book' },
        { label: 'Settings', href: '/settings', icon: 'Settings' },
      ]}
      user={currentUser}
    />
    <Header
      breadcrumbs={breadcrumbs}
      showSearch={true}
      showNotifications={true}
      user={currentUser}
    />
    <Dashboard />
  </Shell>
</App>

Admin App Layout

CRUD-focused interface:

<App name="Admin Panel" auth={{ provider: 'clerk' }}>
  <Shell>
    <Sidebar
      items={[
        { label: 'Dashboard', href: '/', icon: 'LayoutDashboard' },
        {
          label: 'Collections',
          icon: 'Database',
          children: [
            { label: 'Posts', href: '/posts' },
            { label: 'Pages', href: '/pages' },
            { label: 'Media', href: '/media' },
          ]
        },
        {
          label: 'Access',
          icon: 'Shield',
          children: [
            { label: 'Users', href: '/users' },
            { label: 'Roles', href: '/roles' },
          ]
        },
        { label: 'Settings', href: '/settings', icon: 'Settings' },
      ]}
      user={currentUser}
    />
    <Header breadcrumbs={breadcrumbs} user={currentUser} />
    <CollectionView collection="posts" />
  </Shell>
</App>

SaaS App Layout

Multi-tenant product:

<App name="Acme SaaS" auth={{ provider: 'auth0' }}>
  <Shell>
    <Sidebar
      items={[
        { label: 'Dashboard', href: '/', icon: 'LayoutDashboard' },
        { label: 'Projects', href: '/projects', icon: 'Folder' },
        { label: 'Team', href: '/team', icon: 'Users' },
        { label: 'Billing', href: '/billing', icon: 'CreditCard' },
        { label: 'Settings', href: '/settings', icon: 'Settings' },
      ]}
      user={currentUser}
      showWorkspaceSwitcher={true}
    />
    <Header
      breadcrumbs={breadcrumbs}
      showSearch={true}
      showNotifications={true}
      user={currentUser}
    />
    <Dashboard />
  </Shell>
</App>

CRM App Layout

Customer relationship management:

<App name="CRM" auth={{ provider: 'workos' }}>
  <Shell>
    <Sidebar
      items={[
        { label: 'Dashboard', href: '/', icon: 'LayoutDashboard' },
        { label: 'Contacts', href: '/contacts', icon: 'Users' },
        { label: 'Deals', href: '/deals', icon: 'TrendingUp' },
        { label: 'Activities', href: '/activities', icon: 'Calendar' },
        { label: 'Reports', href: '/reports', icon: 'BarChart' },
        { label: 'Settings', href: '/settings', icon: 'Settings' },
      ]}
      user={currentUser}
    />
    <Header breadcrumbs={breadcrumbs} user={currentUser} />
    <ContactsView />
  </Shell>
</App>

VibeCode Layout

Three-panel development environment:

<App name="VibeCode" auth={{ provider: 'clerk' }}>
  <Shell
    sidebar={{ visible: false }}
    header={{ visible: true }}
  >
    <Header
      title="VibeCode"
      showSearch={false}
      user={currentUser}
    />
    <VibeCodeView>
      <ChatPanel />
      <PreviewPanel />
      <CodePanel />
    </VibeCodeView>
  </Shell>
</App>

Responsive Layout

The layout system adapts automatically to different screen sizes.

Desktop (≥1024px)

Full layout with expanded sidebar:

┌────────────────────────────────────┐
│            Header                  │
├─────────┬──────────────────────────┤
│ Sidebar │        Main              │
│ (240px) │                          │
│         │                          │
└─────────┴──────────────────────────┘

Tablet (768px - 1023px)

Collapsed sidebar:

┌────────────────────────────────────┐
│            Header                  │
├───┬────────────────────────────────┤
│ S │          Main                  │
│ b │                                │
│   │                                │
└───┴────────────────────────────────┘

Mobile (under 768px)

Sidebar as overlay drawer:

┌────────────────────────────────────┐
│ ☰  Header                          │
├────────────────────────────────────┤
│                                    │
│            Main                    │
│                                    │
└────────────────────────────────────┘

Customization

Custom Sidebar Width

<Shell
  sidebar={{
    width: 280,
    collapsedWidth: 80,
  }}
>
  <Dashboard />
</Shell>

Custom Header Height

<Shell
  header={{
    height: 72,
  }}
>
  <Dashboard />
</Shell>

Custom Breakpoints

Control responsive behavior with CSS variables:

<Shell
  className="
    [--sidebar-width:280px]
    [--sidebar-collapsed-width:80px]
    [--header-height:72px]
  "
>
  <Dashboard />
</Shell>

Theme Variants

<Shell variant="minimal">
  <Dashboard />
</Shell>

<Shell variant="compact">
  <Dashboard />
</Shell>

State Management

The Shell manages sidebar collapse state:

function MyApp() {
  const [collapsed, setCollapsed] = useState(false)

  return (
    <Shell
      sidebar={{
        collapsed,
        onCollapse: () => setCollapsed(!collapsed),
        persistState: true, // Save to localStorage
      }}
    >
      <Dashboard />
    </Shell>
  )
}

Track active routes and breadcrumbs:

function useNavigation() {
  const pathname = usePathname()

  const breadcrumbs = generateBreadcrumbs(pathname)
  const activeNavItem = findActiveNavItem(pathname)

  return { breadcrumbs, activeNavItem }
}

function MyApp() {
  const { breadcrumbs, activeNavItem } = useNavigation()

  return (
    <Shell>
      <Header breadcrumbs={breadcrumbs} />
      <Dashboard />
    </Shell>
  )
}

Accessibility

The layout system implements accessibility best practices:

  • Landmark regions - Proper semantic HTML (<header>, <nav>, <main>)
  • Skip links - Jump to main content
  • Keyboard navigation - Full keyboard support
  • Focus management - Visible focus indicators
  • Screen reader labels - Descriptive ARIA labels
  • Responsive - Mobile-friendly navigation

Example markup:

<div role="application">
  <a href="#main" class="skip-link">Skip to content</a>

  <header role="banner">
    <!-- Header content -->
  </header>

  <nav role="navigation" aria-label="Main navigation">
    <!-- Sidebar navigation -->
  </nav>

  <main id="main" role="main">
    <!-- Main content -->
  </main>
</div>

Best Practices

Keep Layout Declarative

Define layout structure upfront:

// Good - clear layout structure
<App name="Dashboard">
  <Shell>
    <Dashboard />
  </Shell>
</App>

// Bad - conditional layout
<App name="Dashboard">
  {showSidebar ? (
    <Shell>
      <Dashboard />
    </Shell>
  ) : (
    <Dashboard />
  )}
</App>

Use Consistent Navigation

Maintain the same navigation structure across the app:

// Good - consistent nav structure
const navigation = [
  { label: 'Dashboard', href: '/', icon: 'LayoutDashboard' },
  { label: 'Settings', href: '/settings', icon: 'Settings' },
]

// Use the same navigation everywhere
<Sidebar items={navigation} />

Handle Mobile Navigation

Ensure mobile users can access all features:

<Shell
  sidebar={{
    // Automatically handle mobile drawer
    persistState: true,
  }}
>
  <Dashboard />
</Shell>

Limit Breadcrumb Depth

Keep breadcrumbs shallow (3-4 levels max):

// Good - shallow hierarchy
{ label: 'Dashboard', href: '/' }
{ label: 'Projects', href: '/projects' }
{ label: 'Acme Website' }

// Bad - too deep
{ label: 'Dashboard', href: '/' }
{ label: 'Workspace', href: '/workspace' }
{ label: 'Category', href: '/workspace/category' }
{ label: 'Subcategory', href: '/category/sub' }
{ label: 'Projects', href: '/sub/projects' }
{ label: 'Acme Website' }

Use View Components

Wrap content in semantic View components:

// Good - semantic views
<Shell>
  <DashboardView>
    <MetricsPanel />
    <ChartPanel />
  </DashboardView>
</Shell>

// Bad - raw content
<Shell>
  <div>
    <h1>Dashboard</h1>
    <p>Metrics...</p>
  </div>
</Shell>
  • App - Root application wrapper with authentication
  • Shell - Application shell layout
  • Sidebar - Navigation sidebar
  • Header - Top header bar with breadcrumbs
  • Dashboard - Main dashboard view

On this page