Components / Application
App Layout
Open full-screen previewThe full application shell: a collapsible forest-green sidebar (workspace switcher, searchable navigation with Settings drill-in, an upcoming events strip, onboarding checklist, user menu) beside a content area with its own header. Built on the shadcn sidebar (sidebar-07 pattern) with Instant Strata branding. Drag the handle below to resize the layout, or use the trigger in the header to collapse the sidebar to icons.
This is the strata manager's experience. See the same shell built out for an individual owner on the Owner Portal page.
Live layout
Drag the handle to resize the layout
Installation
Installs the branded shell pieces and pulls the shadcn sidebar, dropdown-menu and separator dependencies. Pair with the SidebarOnboarding and SidebarUpcoming elements for the setup checklist and upcoming events strip.
npx shadcn add https://raw.githubusercontent.com/levi-putna/elements/main/registry/app-shell/registry.json
npx shadcn add https://raw.githubusercontent.com/levi-putna/elements/main/registry/sidebar-upcoming/registry.jsonAnatomy
SidebarProviderHolds open/collapsed state, the ⌘B shortcut, and the mobile sheet. Wrap the whole layout. Set forest sidebar tokens here when using Instant Strata branding.SidebarThe sidebar shell. collapsible="icon" collapses to an icon rail; on mobile it becomes a slide-in sheet.AppSidebarHeaderWorkspace switcher in the header slot: lime logo mark, workspace name, and a dropdown to switch workspaces.SidebarNavThe navigation with optional search overlay, inline collapsible sub-items, and drill-in stacked panels (e.g. Settings).SidebarUpcomingOptional strip below the nav: upcoming events from today through the next few days. Collapsed by default with a count; expand for up to five rows and a view-all link. Hidden when collapsed to icons.SidebarOnboardingOptional setup checklist in the footer slot, above the user menu. Collapses to a progress row when the sidebar is icon-only.AppSidebarFooterUser menu in the footer slot: avatar initials, name/email, and an account dropdown.AppHeaderTop bar inside the content area: holds the SidebarTrigger and a breadcrumb/title slot.SidebarInsetThe main content region beside the sidebar.SidebarRailA thin draggable rail on the sidebar edge for toggling collapse.Sidebar search
SidebarNav includes a search overlay at the top of the root navigation. Click the search field or press ⌘K to open it. Results filter client-side across root links, inline sub-items, drill-in panel pages (e.g. Settings → Billing), and optional entity rows passed via searchExtras. The overlay sits above stacked nav panels without disturbing the back animation. Set overflow-hidden on SidebarContent so the overlay clips correctly.
// SidebarNav includes a search overlay by default.
// Click the search field at the top of the nav, or press ⌘K.
// Results filter across root links, drill-in panel items, and
// optional entity rows passed via searchExtras.
<SidebarContent className="overflow-hidden">
<SidebarNav
groups={nav}
searchExtras={[
{
title: "Harbour View Towers",
subtitle: "SP 48291 · 42 lots",
href: "/schemes/harbour-view",
kind: "scheme",
},
{
title: "Lot 14",
subtitle: "Harbour View Towers · Levy due",
href: "/lots/14",
kind: "lot",
},
]}
searchPlaceholder="Search schemes, lots, owners…"
/>
</SidebarContent>
// Disable search on a minimal shell:
<SidebarNav groups={nav} search={false} />Upcoming events
SidebarUpcoming sits below the main nav inside SidebarContent. It filters to today and the next five days, shows at most five rows when expanded, and ends with a view-all link. Collapsed by default, the section shows the total upcoming count; click to expand the list. Typography is deliberately lighter than primary nav items so agents can scan what is ahead without competing with wayfinding.
import { SidebarUpcoming } from "@/components/ui/sidebar-upcoming"
// Below SidebarNav, inside SidebarContent. Shows today + next 5 days,
// max 5 rows, then a view-all link. Hidden when the sidebar is icon-only.
<SidebarContent className="flex flex-col overflow-hidden">
<div className="min-h-0 flex-1 overflow-hidden">
<SidebarNav groups={nav} />
</div>
<SidebarUpcoming
events={[
{
id: "agm",
title: "AGM · Harbour View Towers",
href: "/meetings/agm",
date: "2026-06-17",
time: "6:00 pm",
scheme: "Committee",
kind: "agm",
},
]}
viewAllHref="/calendar"
viewAllLabel="View calendar"
/>
</SidebarContent>Composition
import {
Sidebar,
SidebarContent,
SidebarInset,
SidebarProvider,
SidebarRail,
} from "@/components/ui/sidebar"
import {
AppHeader,
AppSidebarHeader,
AppSidebarFooter,
SidebarNav,
type NavGroup,
type NavPanel,
} from "@/components/ui/app-shell"
import { SidebarOnboarding } from "@/components/ui/onboarding"
import { SidebarUpcoming } from "@/components/ui/sidebar-upcoming"
import {
LayoutDashboard,
MessageSquare,
Settings,
SlidersHorizontal,
Users,
} from "lucide-react"
const settingsPanel: NavPanel = {
title: "Settings",
groups: [
{
label: "Workspace",
items: [
{ title: "General", href: "/settings", icon: SlidersHorizontal, isActive: true },
{ title: "Members", href: "/settings/members", icon: Users },
],
},
],
}
const nav: NavGroup[] = [
{
items: [
{ title: "Dashboard", href: "/", icon: LayoutDashboard, isActive: true },
{ title: "Inbox", href: "/inbox", icon: MessageSquare },
{
title: "Settings",
href: "/settings",
icon: Settings,
panel: settingsPanel,
},
],
},
]
export default function AppLayout({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider
style={{
"--sidebar": "#043F2E",
"--sidebar-foreground": "#EEF2E3",
"--sidebar-primary": "#C8F169",
"--sidebar-primary-foreground": "#043F2E",
"--sidebar-accent": "#0A5C3D",
"--sidebar-accent-foreground": "#FFFFFF",
"--sidebar-border": "#032B1F",
"--sidebar-ring": "#C8F169",
} as React.CSSProperties}
>
<Sidebar collapsible="icon">
<AppSidebarHeader
workspace={{ name: "Instant Strata", plan: "Pro plan" }}
workspaces={[
{ name: "Instant Strata", plan: "Pro plan" },
{ name: "Harbour Body Corp", plan: "Free plan" },
]}
/>
<SidebarContent className="flex flex-col overflow-hidden">
<div className="min-h-0 flex-1 overflow-hidden">
<SidebarNav groups={nav} searchPlaceholder="Search…" />
</div>
<SidebarUpcoming
events={upcomingEvents}
viewAllHref="/calendar"
viewAllLabel="View calendar"
/>
</SidebarContent>
<SidebarOnboarding steps={onboardingSteps} />
<AppSidebarFooter
user={{ name: "Levi Putna", email: "levi@instantstrata.com" }}
/>
<SidebarRail />
</Sidebar>
<SidebarInset>
<AppHeader>
<Breadcrumbs />
</AppHeader>
<div className="flex-1 overflow-auto p-4">{children}</div>
</SidebarInset>
</SidebarProvider>
)
}Resizable preview
The demo above wraps the layout in a ResizablePanelGroup so you can drag to see how the content reflows; the same technique the shadcn blocks gallery uses. In production you mount the layout directly at full height.
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable"
// In the docs we wrap the whole layout in a Resizable panel group so
// you can drag to see how the inset and content reflow. In a real app
// you mount <AppLayout> directly at full height.
<ResizablePanelGroup orientation="horizontal" className="h-[600px]">
<ResizablePanel defaultSize="78%" minSize="35%">
<AppLayout>{children}</AppLayout>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize="22%" minSize="8%">
{/* drag affordance */}
</ResizablePanel>
</ResizablePanelGroup>SidebarNav Props
| Prop | Type | Default | Description |
|---|---|---|---|
| groups | NavGroup[] | - | Labelled groups of nav items. NavItem = { title, href, icon?, isActive?, items?, panel? }. |
| search | boolean | true | Show the sidebar search overlay on the root nav. Opens via the search field or ⌘K. |
| searchPlaceholder | string | - | Placeholder copy for the search input. |
| searchExtras | NavSearchResult[] | - | Extra searchable rows for schemes, lots, owners, documents, and other entities not shown in the main nav. |
| className | string | - | Additional classes applied to each SidebarGroup. |
SidebarUpcoming Props
| Prop | Type | Default | Description |
|---|---|---|---|
| events | UpcomingEvent[] | - | Events to consider. Each row: { id, title, href, date (YYYY-MM-DD), time?, scheme?, kind? }. |
| maxItems | number | 5 | Maximum rows shown after filtering and sorting. |
| horizonDays | number | 5 | Include events from today through this many calendar days (inclusive). |
| title | string | "Upcoming" | Section heading. |
| viewAllHref | string | - | Optional href for the view-all link at the bottom. |
| viewAllLabel | string | "View all" | Label for the view-all link. |
| defaultOpen | boolean | false | Whether the event list starts expanded. When collapsed, only the section heading and upcoming count are shown. |
| className | string | - | Additional classes on the section wrapper. |
AppHeader Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | - | Breadcrumbs or page title, rendered to the right of the trigger. |
| actions | ReactNode | - | Optional actions pinned to the right edge of the bar. |
| className | string | - | Additional classes on the header element. |