Skip to content

Layout and Navigation

import { LayoutBase, NavBase, CardBase, GridBase } from "@lastshotlabs/snapshot/ui";
function AppShell({ children }) {
return (
<LayoutBase
variant="sidebar"
nav={
<NavBase
variant="sidebar"
logo={{ text: "My App", path: "/" }}
items={[
{ label: "Dashboard", path: "/", icon: "home" },
{ label: "Users", path: "/users", icon: "users" },
{ label: "Settings", path: "/settings", icon: "settings" },
]}
collapsible
/>
}
>
{children}
</LayoutBase>
);
}

Wraps your page content with nav, header, sidebar, main, and footer regions.

<LayoutBase variant="sidebar" nav={<NavBase ... />}>
<h1>Page content</h1>
</LayoutBase>

Variants:

VariantDescription
sidebarNav on the left, content on the right
top-navNav on top, content below
stackedVertical stack: header, sidebar, main, footer
minimalCentered single column
centeredCentered, max-width constrained
full-widthNo constraints, full viewport

Layout slots for advanced composition:

<LayoutBase
variant="sidebar"
nav={<NavBase ... />}
layoutSlots={{
header: <AnnouncementBar />,
footer: <Footer />,
}}
>
<Dashboard />
</LayoutBase>

Main navigation component supporting sidebar and top-nav layouts.

<NavBase
variant="sidebar"
logo={{ text: "My App", src: "/logo.svg", path: "/" }}
items={[
{ label: "Dashboard", path: "/", icon: "home" },
{ label: "Users", path: "/users", icon: "users", badge: "12" },
{ label: "Reports", path: "/reports", icon: "bar-chart", children: [
{ label: "Revenue", path: "/reports/revenue" },
{ label: "Growth", path: "/reports/growth" },
]},
{ label: "Settings", path: "/settings", icon: "settings" },
]}
collapsible
pathname={currentPath}
onNavigate={(path) => router.push(path)}
/>
<NavBase
variant="top-nav"
logo={{ text: "My App", path: "/" }}
items={[
{ label: "Home", path: "/" },
{ label: "Products", path: "/products" },
{ label: "Pricing", path: "/pricing" },
]}
>
<ButtonBase label="Sign in" variant="outline" />
</NavBase>
PropTypeDescription
labelstringDisplay text
pathstringNavigation path
iconstringIcon name
badgestringBadge text
disabledbooleanDisable the item
activebooleanForce active state
visiblebooleanShow/hide the item
childrenNavBaseItem[]Nested sub-items

Individual nav link for custom navigation layouts.

import { NavLinkBase } from "@lastshotlabs/snapshot/ui";
<NavLinkBase
path="/users"
label="Users"
icon="users"
badge="12"
active={pathname === "/users"}
onNavigate={(path) => router.push(path)}
/>

User dropdown menu with avatar for auth-aware navigation.

import { NavUserMenuBase } from "@lastshotlabs/snapshot/ui";
<NavUserMenuBase
userName={user.name}
userEmail={user.email}
userAvatar={user.avatarUrl}
showAvatar
showName
items={[
{ label: "Profile", icon: "user", onClick: () => (window.location.href = "/profile") },
{ label: "Settings", icon: "settings", onClick: () => (window.location.href = "/settings") },
{ label: "Sign out", icon: "log-out", onClick: () => logout() },
]}
/>

CSS Grid container.

import { GridBase } from "@lastshotlabs/snapshot/ui";
<GridBase columns={3} gap="md">
<CardBase title="Revenue">$48,200</CardBase>
<CardBase title="Users">1,234</CardBase>
<CardBase title="Orders">567</CardBase>
</GridBase>

Responsive columns: Pass a string for responsive grid:

<GridBase columns="repeat(auto-fill, minmax(300px, 1fr))" gap="lg">
{items.map((item) => <CardBase key={item.id} title={item.name} />)}
</GridBase>

Flexbox row and column containers.

import { RowBase, ColumnBase } from "@lastshotlabs/snapshot/ui";
<RowBase gap="md" align="center" justify="between">
<h1>Users</h1>
<ButtonBase label="Add User" icon="plus" />
</RowBase>
<ColumnBase gap="lg">
<SectionOne />
<SectionTwo />
</ColumnBase>

Styled card with optional title and subtitle.

<CardBase title="Project Details" subtitle="Last updated 2 hours ago" gap="md">
<p>Card content</p>
</CardBase>

Centered, max-width wrapper for page content.

import { ContainerBase } from "@lastshotlabs/snapshot/ui";
<ContainerBase maxWidth="lg" padding="xl">
<h1>Page Title</h1>
<p>Content is centered and constrained.</p>
</ContainerBase>

Max-width presets: xs, sm, md, lg, xl, 2xl, full, prose

Generic element wrapper with customizable HTML tag.

import { BoxBase } from "@lastshotlabs/snapshot/ui";
<BoxBase as="section" className="hero-section">
<h1>Welcome</h1>
</BoxBase>

HTML tags: div, section, article, aside, header, footer, main, nav, span

Full-width vertical section.

import { SectionBase } from "@lastshotlabs/snapshot/ui";
<SectionBase height="screen" align="center" justify="center">
<h1>Full-height hero</h1>
</SectionBase>

Empty spacing element.

import { SpacerBase } from "@lastshotlabs/snapshot/ui";
<ColumnBase>
<Header />
<SpacerBase size="xl" />
<Content />
</ColumnBase>

Spacing tokens: 2xs, xs, sm, md, lg, xl, 2xl

Resizable two-pane split.

import { SplitPaneBase } from "@lastshotlabs/snapshot/ui";
<SplitPaneBase
direction="horizontal"
defaultSplit={30}
minSize={200}
first={<Sidebar />}
second={<MainContent />}
/>

Animated expand/collapse.

import { CollapsibleBase } from "@lastshotlabs/snapshot/ui";
<CollapsibleBase
trigger={<ButtonBase label="Show details" variant="ghost" />}
defaultOpen={false}
duration="normal"
>
<p>Hidden content that expands on click.</p>
</CollapsibleBase>

For mobile, collapse the sidebar into a hamburger menu using DrawerBase:

import { DrawerBase, IconButtonBase, LayoutBase, NavBase, RowBase } from "@lastshotlabs/snapshot/ui";
import { useState, useEffect } from "react";
const NAV_ITEMS = [
{ label: "Dashboard", path: "/", icon: "home" },
{ label: "Users", path: "/users", icon: "users" },
{ label: "Settings", path: "/settings", icon: "settings" },
];
function ResponsiveShell({ children }: { children: React.ReactNode }) {
const [mobileOpen, setMobileOpen] = useState(false);
const isMobile = useMediaQuery("(max-width: 768px)");
if (isMobile) {
return (
<>
<RowBase
justify="between"
align="center"
style={{ padding: "0.75rem 1rem", borderBottom: "1px solid var(--sn-color-border)" }}
>
<IconButtonBase icon="menu" ariaLabel="Open menu" onClick={() => setMobileOpen(true)} />
<span style={{ fontWeight: 600 }}>My App</span>
<span style={{ width: 40 }} />
</RowBase>
<DrawerBase
title="Navigation"
side="left"
size="sm"
open={mobileOpen}
onClose={() => setMobileOpen(false)}
>
<NavBase
variant="sidebar"
items={NAV_ITEMS}
onNavigate={(path) => { window.location.href = path; setMobileOpen(false); }}
/>
</DrawerBase>
<div style={{ padding: "1rem" }}>{children}</div>
</>
);
}
return (
<LayoutBase
variant="sidebar"
nav={<NavBase variant="sidebar" logo={{ text: "My App", path: "/" }} items={NAV_ITEMS} collapsible />}
>
{children}
</LayoutBase>
);
}
// Simple media query hook
function useMediaQuery(query: string) {
const [matches, setMatches] = useState(() =>
typeof window !== "undefined" ? window.matchMedia(query).matches : false
);
useEffect(() => {
const mql = window.matchMedia(query);
const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
mql.addEventListener("change", handler);
return () => mql.removeEventListener("change", handler);
}, [query]);
return matches;
}

On desktop, this renders the standard sidebar. On mobile, the sidebar becomes a drawer that slides in from the left when the hamburger icon is tapped.

function App() {
const { user } = snap.useUser();
const { mutate: logout } = snap.useLogout();
return (
<LayoutBase
variant="sidebar"
nav={
<NavBase
variant="sidebar"
logo={{ text: "Admin", src: "/logo.svg", path: "/" }}
items={[
{ label: "Dashboard", path: "/", icon: "home" },
{ label: "Users", path: "/users", icon: "users" },
{ label: "Reports", path: "/reports", icon: "bar-chart" },
]}
collapsible
onNavigate={(path) => router.push(path)}
/>
}
>
<ContainerBase maxWidth="xl" padding="lg">
<RowBase justify="between" align="center">
<h1>Dashboard</h1>
<NavUserMenuBase
userName={user?.name}
userAvatar={user?.avatarUrl}
items={[
{ label: "Settings", icon: "settings", onClick: () => (window.location.href = "/settings") },
{ label: "Sign out", icon: "log-out", onClick: () => logout() },
]}
/>
</RowBase>
<SpacerBase size="lg" />
<GridBase columns={3} gap="md">
<StatCardBase label="Revenue" value="$48K" trend={{ direction: "up", value: "+12%" }} />
<StatCardBase label="Users" value="1,234" trend={{ direction: "up", value: "+5%" }} />
<StatCardBase label="Orders" value="567" trend={{ direction: "down", value: "-3%" }} />
</GridBase>
</ContainerBase>
</LayoutBase>
);
}
ComponentDescription
LayoutBaseApp shell with 6 layout variants
NavBaseSidebar and top-nav navigation
NavLinkBaseIndividual nav link
NavUserMenuBaseUser dropdown menu
GridBaseCSS Grid container
RowBaseFlex row
ColumnBaseFlex column
CardBaseStyled card
ContainerBaseCentered max-width wrapper
BoxBaseGeneric element wrapper
SectionBaseFull-width section
SpacerBaseSpacing element
SplitPaneBaseResizable split panes
CollapsibleBaseExpand/collapse