Skip to Content
glob.ui home
Layouts

AppShellLayout

An app shell for dashboards and admin apps. The viewport never scrolls: the header and sidebar stay in place, and only the main region scrolls.

Client componentUses state or handlers, so it starts with "use client".
import { AppShellLayout } from "@glob-ui/app-shell-layout"

Anatomy

Each slot declares its scroll behaviour. Switch on Anatomy to see them, and Fill with content to see which region scrolls.

import { AppShellLayout } from "@glob-ui/app-shell-layout"import { LayoutNavTrigger } from "@glob-ui/layout"import { PageLayout, PageHeader } from "@glob-ui/page-layout"const ROWS = Array.from({ length: 24 }, (_, i) => `Order ${1000 + i}`)export default function AppShellDefault() {  return (    <AppShellLayout      header={        <>          <LayoutNavTrigger />          <strong>Acme</strong>        </>      }      sidebar={        <>          <a href="#overview" aria-current="page" className="rounded-md px-3 py-2 text-sm no-underline hover:bg-fd-accent">            Overview          </a>          <a href="#orders" className="rounded-md px-3 py-2 text-sm no-underline hover:bg-fd-accent">            Orders          </a>          <a href="#customers" className="rounded-md px-3 py-2 text-sm no-underline hover:bg-fd-accent">            Customers          </a>        </>      }    >      <PageLayout header={<PageHeader title="Orders" />}>        <ul className="flex flex-col gap-2">          {ROWS.map((row) => (            <li key={row} className="rounded-lg border p-3">              {row}            </li>          ))}        </ul>      </PageLayout>    </AppShellLayout>  )}

Slots

The layout is a grid of slots. A prop fills each slot, and each slot is fixed or scroll.

AppShellLayout: each slot is fixed or scroll. A prop fills it.
SlotElementScrollNotes
header<header>fixedBrand, the navigation trigger on small screens, account.
sidebar<nav>fixedNavigation. Hidden below lg, where it moves into a Sheet. Its list is the only scroller inside it.
main<main>scrollThe page. The one region that scrolls. It has no padding: put a PageLayout in it.
  • fixed: the slot never scrolls. It stays in place, or sticks.
  • scroll: the one region that scrolls for this part of the screen.

Mobile navigation

Put LayoutNavTrigger in the header. It hides itself at lg and up, where the sidebar is shown inline. Below lg the sidebar content opens in a Sheet from the left.

<AppShellLayout header={<><LayoutNavTrigger /><Brand /></>} sidebar={<NavLinks />}>
  <Page />
</AppShellLayout>

API

AppShellLayoutProps

Prop

Type

Accessibility

Key or actionBehaviour
Skip to contentThe first Tab stop. Enter moves focus to main.
Navigation triggerA button named "Open navigation" with aria-expanded. It opens the navigation in a Sheet below lg.
SheetFocus moves into the navigation. Escape returns focus to the trigger. A link inside it closes the Sheet.

ARIA. Landmarks: banner for the header, navigation named by sidebarLabel, and one main. Only the main region is a keyboard stop for scrolling.

On this page