Skip to content

App Shell

AppShell implements the two-level navigation architecture (ED-11), structured to match the reference shell in helm-designsystem (src/app/Root.tsx). The full-height vertical left rail is Tower’s — brand block at its top, then the primary nav, the Modules section, the org-profile block, and the collapse toggle. The main column carries a 66px header (page title + global actions), the current module’s 40px horizontal tab strip, and the content zone, which hosts each page’s Page shell: flush Left/Right panels plus the padded middle column, the only region that scrolls independently. The shell lays this out from the page’s PageContract (epic #2316); pages no longer assemble it themselves. Every page inside a module is wrapped in AppShell.

Live demo

Usage

import { AppShell } from "@beacon/design-system";
import type { NavItem } from "@beacon/design-system";
import { useNavigate, useMatch } from "react-router-dom";
function Shell({ children }: { children: React.ReactNode }) {
const navigate = useNavigate();
const onLien = useMatch("/lien/*");
const primaryNav: NavItem[] = [
{ id: "home", label: "Home", active: !onLien, onClick: () => navigate("/") },
];
const modulesNav: NavItem[] = [
{ id: "lien", label: "Lien", active: !!onLien, onClick: () => navigate("/lien/dashboard") },
];
const lienSecondaryNav: NavItem[] = [
{ id: "dashboard", label: "Dashboard", active: true, onClick: () => navigate("/lien/dashboard") },
];
return (
<AppShell
primaryNav={primaryNav}
modulesNav={modulesNav}
secondaryNav={onLien ? lienSecondaryNav : undefined}
secondaryNavLabel="Lien & Collections"
pageTitle="Lien & Collections"
brandTitle="Helm"
onBrandClick={() => navigate("/")}
>
{children}
</AppShell>
);
}

Props

PropTypeDefaultDescription
primaryNavNavItem[]—Tower-owned vertical nav items (left rail). Required.
modulesNavNavItem[]undefinedThe rail’s “Modules” section: one entry per enabled module.
utilityNavNavItem[]undefinedRail items pinned below a separator (Settings, Admin).
secondaryNavNavItem[]undefinedModule-owned horizontal tabs (40px strip). Optional.
secondaryNavLabelstringundefinedUppercase module label in the strip’s leading cell.
brandMarkReactNodebrand.mark icon roleMark rendered on the 34px primary-color tile at the top of the rail.
brandTitleReactNodeundefinedProduct wordmark next to the tile (hidden when collapsed).
brandSubtitleReactNodeundefinedSmall uppercase line under the wordmark.
onBrandClick() => voidundefinedClick handler for the brand block (conventionally: Home).
brandReactNodeundefinedFully custom brand block (product skins); replaces mark/title.
pageTitleReactNodeundefinedPage/module context at the left of the 66px header.
topBarActionsReactNodeundefinedRight-aligned header content (search, notifications, org).
sidebarFooterReactNodeundefinedOrg-profile block above the collapse toggle.
footerReactNodeundefinedFull-width app footer.
childrenReactNode—Page content rendered in the scrolling main area.
FieldTypeDefaultDescription
idstring—Stable key.
labelstring—Display text.
iconReactNodeundefinedExplicit icon node; wins over iconRole.
iconRoleIconRoleundefinedThemeable icon role from the token store, rendered via RoleIcon at 16px.
activebooleanundefinedMarks the currently selected item.
onClick() => voidundefinedCalled when the item is clicked. Use with your router’s navigate() — AppShell is router-agnostic.

Design tokens used

AppShell.module.css reads exclusively from store tokens. No hardcoded colors.

TokenRole
--sidebar / --sidebar-borderRail, header, and footer chrome surfaces (mirror card/border so the shell follows the theme).
--helm-amber-dim / --helm-amberActive nav item background / text, module tab underline.
--helm-s2 / --helm-s3Module-nav strip background / hover background.
--helm-text-dim / --muted-foregroundInactive nav text / section labels and captions.
--sidebar-primaryBrand tile background (mirrors primary).
--top-bar-height / --secondary-nav-height / --sidebar-width66px header, 40px strip, 228px/52px rail.
--page-padding-y / --page-padding-x28px/32px page padding, applied by the Page shell’s middle column (layout canon).
--duration-fast / --duration-base / --easing-ease120ms color feedback, 200ms rail collapse on the standard curve.

Architecture note

Tower builds primaryNav/modulesNav from the tenant’s enabled modules. Individual modules supply secondaryNav for their own views. Neither layer should know about the other’s items. This enforces the module boundary: a module cannot add itself to Tower’s primary nav, and Tower cannot dictate a module’s secondary nav.