Passer au contenu principal

Adminlayout

Implemented

Admin application frame with a skip link, sidebar and header slots, and a focusable, scrollable main content region.

Categoryorganisms
Sourcecomponents/organisms/AdminLayout/AdminLayout.tsx
AI HintAdmin application frame: compose the Sidebar molecule (ADR-035 — mobile drawer, portal + scrim + focus trap + Escape + scroll lock) in the sidebar slot and AdminHeader in the header slot; the skip-link label and main aria-label default English-literal and are overridable via skipLabel/mainAriaLabel; the main region is a focusable (tabIndex -1) scroll container.

Admin application frame — a skip link, sidebar and header slots, and a focusable, scrollable main content region; compose the Sidebar molecule in the sidebar slot. The skip-link and main-landmark labels default English-literal and are overridable via skipLabel / mainAriaLabel (zero owned i18n keys).

AdminLayout

Organism — Admin family

The outermost layout organism for admin surfaces: sidebar + header slots (compose the Sidebar molecule / AdminHeader organism), a full-height flex row, a skip link to the main region, and a focusable (tabIndex -1) scrolling <main> landmark. The header column is min-w-0 so long tables truncate instead of blowing out the layout. AdminSidebar is desktop-only legacy kept for existing frames — not for new work (prefer the Sidebar molecule, whose portal drawer + scrim + focus trap the storefront dashboard adopted in DS-CORR-08-05; formal deprecation decision pending).

skipLabel: “Skip to main content” (default) — mainAriaLabel: “Main content” (default). Tab into the frame to surface the skip link.

Skip to main content

Sidebar slot

Overview

Orders

Events

Team

AdminHeader slot

⌘K — command palette trigger lives here

Main content region

Page content lands in a focusable <main> landmark with its own horizontal scroll — broad admin tables scroll inside the frame instead of the page. Focus it programmatically on route change.

This component supports light and dark mode.

Usage

AdminLayout is the frame, not the page: fill the sidebar slot with the Sidebar molecule (pass collapsible so the frame works on mobile) and the header slot with AdminHeader, then render the route inside children. The frame owns zero i18n keys — override skipLabel / mainAriaLabel with translated strings when the app language isn't English, and pass AdminHeader its own required locale for its command-palette label.

import { AdminLayout, AdminHeader, Sidebar, type SidebarGroup } from '@epasslive/design-system'

const navGroups: SidebarGroup[] = [
  { id: 'manage', label: 'Manage', items: [
    { id: 'overview', label: 'Overview', href: '/admin' },
    { id: 'orders', label: 'Orders', href: '/admin/orders' },
    { id: 'events', label: 'Events', href: '/admin/events' },
  ] },
  { id: 'account', label: 'Account', items: [
    { id: 'team', label: 'Team', href: '/admin/team' },
    { id: 'settings', label: 'Settings', href: '/admin/settings' },
  ] },
]

<AdminLayout
  skipLabel={locale === 'fr' ? 'Aller au contenu principal' : undefined}
  sidebar={<Sidebar logo={<Logo />} groups={navGroups} activeKey="orders" collapsible />}
  header={<AdminHeader locale={locale} brand={<Logo />} onSearch={openPalette} />}
>
  {/* page content — renders inside <main id="admin-layout-main"> */}
  <OrdersPanel />
</AdminLayout>

Composition notes

The slots are plain ReactNode — the frame imposes no markup on them, so sidebars and headers stay independently swappable. The frame owns zero i18n keys: its only strings (skip-link label, main landmark aria-label) default English-literal and are overridable via skipLabel / mainAriaLabel — while AdminHeader takes its own required locale. The main region owns scrolling (overflow-x-auto with focus:outline-none): focus it on route change and broad tables scroll horizontally inside the frame. Header-slot dropdowns must portal to document.body — the content column clips (overflow-y-hidden). Pass a distinct id if a second frame ever appears, so the skip link still targets the right <main>. AdminLayout owns the "which admin frame" story — AdminShell is the legacy template retained per ADR-035; don't start new work on it.

Responsive behavior

The content column's <main> carries a responsive gutter (p-4 md:p-6) and sits in a min-w-0 flex column, so wide tables truncate or scroll inside the frame instead of blowing it out. Mobile is owned by the sidebar slot: compose the Sidebar molecule with collapsible and a breakpoint ( "lg" default) and it swaps to a portal drawer (scrim + focus trap + Escape + scroll lock) below the breakpoint while staying a persistent sticky rail at and above it.

Migrating from AdminSidebar

AdminSidebar (organism) is desktop-only legacy, kept for existing frames — not for new work (the storefront dashboard migrated off it in DS-CORR-08-05). Nothing changes on AdminLayout itself — the slot accepts either; new code should compose Sidebar. The API maps directly: groups becomes SidebarGroup[] (items gain icon, badge, nested children and an ADR-031 accent), activeId becomes activeKey, logo / ariaLabel / className / id are unchanged, and the mobile-drawer props (collapsible, breakpoint, defaultOpen, storageKey, toggleLabel, closeLabel) are new.

Accessibility

Skip-to-content link (WCAG 2.4.1 Bypass Blocks): translated off-canvas until focused (-translate-y-40 → focus:translate-y-0), named by its rendered skipLabel text, targeting the main region's id. The visible state uses the interactive token bg-ep-btn-primary (5.70:1 — AA in both themes) with a focus-visible ring. The main region is a real <main> landmark, focusable via tabIndex={-1} so programmatic focus lands without a visible ring trap (WCAG 2.4.3). Frame colors come from tokens, which meet AA contrast in both themes.

Props

Prop
Type
Default
Description
sidebar*
React.ReactNode
—

Left rail slot — compose the Sidebar molecule here (ADR-035).

header*
React.ReactNode
—

Top bar slot — AdminHeader or a custom bar.

children*
React.ReactNode
—

Main content rendered inside the focusable <main> landmark.

skipLabel
React.ReactNode
"Skip to main content"

Skip-link label — English-literal default; override with a translated node (zero-owned-keys precedent).

mainAriaLabel
string
"Main content"

aria-label on the <main> landmark — English-literal default, overridable.

locale
"en" | "fr"
—

Optional locale forwarded from the consumer for symmetry with AdminHeader / AdminFooter. The frame itself owns zero i18n keys — accepted so callers can spread a single locale to every admin chrome component without TS excess-property errors. Internally unused.

className
string
—

Extra classes on the root.

id
string
"admin-layout"

Base id — the main landmark gets <id>-main; the skip link targets it.

Used By

DashboardShell — storefront (packages/storefront/src/components/dashboard/DashboardShell.tsx) — imports AdminLayout from the barrel and fills the slots with its DashboardSidebar (which composes the Sidebar molecule) and DashboardTopbar, with id="admin-layout".

Props

Preview

Adminlayout

Prop
Type
Required
Default
Description
sidebar
React.ReactNode
Required
—

Left rail slot — compose the Sidebar molecule here (ADR-035).

header
React.ReactNode
Required
—

Top bar slot — AdminHeader or custom bar.

children
React.ReactNode
Required
—

Main content rendered inside the focusable main landmark.

skipLabel
React.ReactNode
Optional
"Skip to main content"

Skip-link label — defaults to "Skip to main content" (English-literal, overridable per DetailPageLayout precedent).

mainAriaLabel
string
Optional
"Main content"

aria-label on the <main> landmark — defaults to "Main content" (English-literal, overridable).

locale
"en" | "fr"
Optional
—

Optional locale forwarded from the consumer for symmetry with sibling admin organisms (AdminHeader, AdminFooter). The frame itself owns zero i18n keys — accepted so callers can spread a single `locale` to every admin chrome component without TS excess-property errors. Internally unused.

className
string
Optional
—

Extra classes on the root.

id
string
Optional
"admin-layout"

Base id — main landmark gets `${id}-main`; default "admin-layout".

Quick use:<Adminlayout />

Accessibility

ARIA rolemain

ARIA

1 rule

WCAG 2.4.1 / 2.4.3

Skip link targets the main landmark id; <main> is focusable via tabIndex -1

Usage

Import

tsxSnippet
import { Adminlayout } from "@epasslive/design-system"

Example 1

tsxSnippet
<AdminLayout sidebar={<Sidebar logo={logo} groups={navGroups} activeKey="orders" collapsible />} header={<AdminHeader locale={locale} brand={brand} onSearch={openPalette} />}>{page}</AdminLayout>
Design System v4.1.0 | ePassLive