App Layout
Scaffold your app using responsive layout components.
<AppLayout>
<AppLayoutHeader>
{/*Header/Navbar*/}
</AppLayoutHeader>
<AppLayoutContent className="container max-w-7xl py-5">
{/*Content*/}
</AppLayoutContent>
<AppLayoutFooter>
{/*Footer*/}
</AppLayoutFooter>
</AppLayout>
Installation
npx @rahimstack@latest add app-layout
Sidebar
AppLayoutSidebar
is used to size and position the sidebar, it should not be styled.AppSidebar
will become a drawer on small screens. (underlg
breakpoint)AppSidebarProvider
is used to share the sidebar state betweenAppSidebar
andAppSidebarTrigger
.AppSidebarTrigger
is used to open/close the sidebar on small screens. It is hidden on large screens.
<AppSidebarProvider>
<AppLayout wideSidebar sidebarSize="md">
<AppLayoutSidebar>
<AppSidebar>
{/*Sidebar content*/}
</AppSidebar>
</AppLayoutSidebar>
<AppLayout>
<AppLayoutHeader>
<AppSidebarTrigger />
{/*Header/Navbar*/}
</AppLayoutHeader>
<AppLayoutContent>
{/*Content*/}
</AppLayoutContent>
<AppLayoutFooter>
{/*Footer*/}
</AppLayoutFooter>
</AppLayout>
</AppLayout>
</AppSidebarProvider>
Close mobile drawer
You might want to close the sidebar automatically when a link is clicked. You can do that by using the useAppSidebarContext
hook.
// layout.tsx
export default function Layout() {
return (
<AppSidebarProvider>
<AppLayout wideSidebar>
<AppLayoutSidebar>
<CustomSidebar/>
</AppLayoutSidebar>
<AppLayout>
{/*...*/}
</AppLayout>
</AppLayout>
</AppSidebarProvider>
)
}
// custom-sidebar.tsx
"use client"
import {useAppSidebarContext} from "@/workshop/app-layout"
export function CustomSidebar() {
const { setOpen } = useAppSidebarContext()
return (
<AppSidebar>
<ScrollArea className="p-4 h-dvh">
<AppSidebarNav>
<VerticalMenu
items={[...]}
onLinkItemClick={() => setOpen(false)}
/>
</AppSidebarNav>
</ScrollArea>
</AppSidebar>
)
}
Sidebar context
You can use the useAppSidebarContext
hook to access or control the sidebar state.
open
: Whether the sidebar is open or closed.setOpen
: Set the sidebar state to open or closed.size
: The size of the sidebar. This overrides thesidebarSize
prop ofAppLayout
.setSize
: Set the size of the sidebar. This overrides thesidebarSize
prop ofAppLayout
.isBelowBreakpoint
: Whether the sidebar is below thelg
breakpoint.
API Reference
const AppLayoutAnatomy = defineStyleAnatomy({
root: cva([
"UI-AppLayout__root appLayout",
"flex w-full group/appLayout",
], {
variants: {
withSidebar: {
true: "flex-row with-sidebar",
false: "flex-col",
},
sidebarSize: {
slim: "sidebar-slim",
sm: "sidebar-sm",
md: "sidebar-md",
lg: "sidebar-lg",
xl: "sidebar-xl",
},
},
defaultVariants: {
withSidebar: false,
sidebarSize: "md",
},
compoundVariants: [
{ withSidebar: true, sidebarSize: "slim", className: "lg:[&>.appLayout]:pl-20" },
{ withSidebar: true, sidebarSize: "sm", className: "lg:[&>.appLayout]:pl-48" },
{ withSidebar: true, sidebarSize: "md", className: "lg:[&>.appLayout]:pl-64" },
{ withSidebar: true, sidebarSize: "lg", className: "lg:[&>.appLayout]:pl-[20rem]" },
{ withSidebar: true, sidebarSize: "xl", className: "lg:[&>.appLayout]:pl-[25rem]" },
],
}),
})
const AppLayoutHeaderAnatomy = defineStyleAnatomy({
root: cva([
"UI-AppLayoutHeader__root",
"relative w-full",
]),
})
const AppLayoutSidebarAnatomy = defineStyleAnatomy({
root: cva([
"UI-AppLayoutSidebar__root",
"hidden lg:fixed lg:inset-y-0 lg:flex lg:flex-col grow-0 shrink-0 basis-0",
"group-[.sidebar-slim]/appLayout:w-20",
"group-[.sidebar-sm]/appLayout:w-48",
"group-[.sidebar-md]/appLayout:w-64",
"group-[.sidebar-lg]/appLayout:w-[20rem]",
"group-[.sidebar-xl]/appLayout:w-[25rem]",
]),
})
const AppLayoutContentAnatomy = defineStyleAnatomy({
root: cva([
"UI-AppLayoutContent__root",
"relative",
]),
})
const AppLayoutFooterAnatomy = defineStyleAnatomy({
root: cva([
"UI-AppLayoutFooter__root",
"relative",
]),
})
const AppLayoutStackAnatomy = defineStyleAnatomy({
root: cva([
"UI-AppLayoutStack__root",
"relative",
], {
variants: {
spacing: {
sm: "space-y-2",
md: "space-y-4",
lg: "space-y-8",
xl: "space-y-10",
},
},
defaultVariants: {
spacing: "md",
},
}),
})
const AppLayoutGridAnatomy = defineStyleAnatomy({
root: cva([
"UI-AppLayoutGrid__root",
"relative flex flex-col",
], {
variants: {
breakBelow: {
sm: "sm:grid sm:space-y-0",
md: "md:grid md:space-y-0",
lg: "lg:grid lg:space-y-0",
xl: "xl:grid xl:space-y-0",
},
spacing: {
sm: "gap-2",
md: "gap-4",
lg: "gap-8",
xl: "gap-10",
},
cols: { 1: null, 2: null, 3: null, 4: null, 5: null, 6: null },
},
defaultVariants: {
breakBelow: "xl",
spacing: "md",
cols: 3,
},
compoundVariants: [
{ breakBelow: "sm", cols: 1, className: "sm:grid-cols-1" },
{ breakBelow: "sm", cols: 2, className: "sm:grid-cols-2" },
{ breakBelow: "sm", cols: 3, className: "sm:grid-cols-3" },
{ breakBelow: "sm", cols: 4, className: "sm:grid-cols-4" },
{ breakBelow: "sm", cols: 5, className: "sm:grid-cols-5" },
{ breakBelow: "sm", cols: 6, className: "sm:grid-cols-6" },
{ breakBelow: "md", cols: 1, className: "md:grid-cols-1" },
{ breakBelow: "md", cols: 2, className: "md:grid-cols-2" },
{ breakBelow: "md", cols: 3, className: "md:grid-cols-3" },
{ breakBelow: "md", cols: 4, className: "md:grid-cols-4" },
{ breakBelow: "md", cols: 5, className: "md:grid-cols-5" },
{ breakBelow: "md", cols: 6, className: "md:grid-cols-6" },
{ breakBelow: "lg", cols: 1, className: "lg:grid-cols-1" },
{ breakBelow: "lg", cols: 2, className: "lg:grid-cols-2" },
{ breakBelow: "lg", cols: 3, className: "lg:grid-cols-3" },
{ breakBelow: "lg", cols: 4, className: "lg:grid-cols-4" },
{ breakBelow: "lg", cols: 5, className: "lg:grid-cols-5" },
{ breakBelow: "lg", cols: 6, className: "lg:grid-cols-6" },
{ breakBelow: "xl", cols: 1, className: "xl:grid-cols-1" },
{ breakBelow: "xl", cols: 2, className: "xl:grid-cols-2" },
{ breakBelow: "xl", cols: 3, className: "xl:grid-cols-3" },
{ breakBelow: "xl", cols: 4, className: "xl:grid-cols-4" },
{ breakBelow: "xl", cols: 5, className: "xl:grid-cols-5" },
{ breakBelow: "xl", cols: 6, className: "xl:grid-cols-6" },
],
}),
})