page-layout-builder
SKILL.md
Page Layout Builder
Generate production-ready page layouts with routing, navigation, and state patterns.
Core Workflow
- Choose page type: Dashboard, auth, settings, CRUD, landing, etc.
- Setup routing: Create route files with proper structure
- Build layout: Header, sidebar, main content, footer
- Add navigation: Nav menus, breadcrumbs, tabs
- State placeholders: Data fetching, forms, modals
- Responsive design: Mobile-first with breakpoints
- Loading states: Skeletons and suspense boundaries
Common Page Patterns
Dashboard Layout
// app/dashboard/layout.tsx
import { Sidebar } from "@/components/Sidebar";
import { Header } from "@/components/Header";
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex h-screen bg-gray-50">
{/* Sidebar - Hidden on mobile, shown on desktop */}
<Sidebar className="hidden lg:flex lg:w-64 lg:flex-col" />
{/* Main Content Area */}
<div className="flex flex-1 flex-col overflow-hidden">
{/* Header */}
<Header />
{/* Page Content */}
<main className="flex-1 overflow-y-auto p-4 md:p-6 lg:p-8">
{children}
</main>
</div>
</div>
);
}
// app/dashboard/page.tsx
import { StatsCard } from "@/components/dashboard/StatsCard";
import { RecentActivity } from "@/components/dashboard/RecentActivity";
import { Chart } from "@/components/dashboard/Chart";
export default function DashboardPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Dashboard</h1>
<p className="text-gray-600">Welcome back! Here's your overview.</p>
</div>
{/* Stats Grid */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<StatsCard title="Total Users" value="2,543" change="+12%" trend="up" />
<StatsCard title="Revenue" value="$45,231" change="+8%" trend="up" />
<StatsCard
title="Active Sessions"
value="431"
change="-3%"
trend="down"
/>
<StatsCard
title="Conversion Rate"
value="3.2%"
change="+0.5%"
trend="up"
/>
</div>
{/* Charts and Activity */}
<div className="grid gap-6 md:grid-cols-2">
<Chart title="Revenue Over Time" />
<RecentActivity title="Recent Activity" />
</div>
</div>
);
}
Authentication Pages
// app/(auth)/layout.tsx
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex min-h-screen">
{/* Left side - Branding (hidden on mobile) */}
<div className="hidden lg:flex lg:w-1/2 lg:flex-col lg:justify-center lg:bg-primary-500 lg:p-12">
<div className="text-white">
<h1 className="text-4xl font-bold">Welcome to AppName</h1>
<p className="mt-4 text-lg text-primary-100">
The best platform for managing your workflow
</p>
</div>
</div>
{/* Right side - Auth form */}
<div className="flex flex-1 flex-col justify-center px-4 py-12 sm:px-6 lg:px-20 xl:px-24">
<div className="mx-auto w-full max-w-sm">{children}</div>
</div>
</div>
);
}
// app/(auth)/login/page.tsx
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import Link from "next/link";
export default function LoginPage() {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
// TODO: Implement authentication logic
try {
// await signIn(formData);
router.push("/dashboard");
} catch (error) {
console.error("Login failed:", error);
} finally {
setIsLoading(false);
}
};
return (
<div className="space-y-6">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-bold">Sign In</h1>
<p className="text-gray-600">
Enter your credentials to access your account
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="you@example.com"
required
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="password">Password</Label>
<Link
href="/forgot-password"
className="text-sm text-primary-600 hover:underline"
>
Forgot password?
</Link>
</div>
<Input
id="password"
type="password"
placeholder="••••••••"
required
/>
</div>
<Button type="submit" className="w-full" isLoading={isLoading}>
Sign In
</Button>
</form>
<div className="text-center text-sm">
Don't have an account?{" "}
<Link href="/register" className="text-primary-600 hover:underline">
Sign up
</Link>
</div>
</div>
);
}
Settings/Profile Page
// app/settings/layout.tsx
import { SettingsSidebar } from "@/components/settings/SettingsSidebar";
export default function SettingsLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="container mx-auto py-6">
<div className="mb-6">
<h1 className="text-3xl font-bold">Settings</h1>
<p className="text-gray-600">
Manage your account settings and preferences
</p>
</div>
<div className="flex flex-col gap-6 lg:flex-row">
<SettingsSidebar className="lg:w-64" />
<div className="flex-1">{children}</div>
</div>
</div>
);
}
// app/settings/profile/page.tsx
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
export default function ProfileSettingsPage() {
const [isSaving, setIsSaving] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSaving(true);
// TODO: Save profile changes
setIsSaving(false);
};
return (
<div className="space-y-6">
<Card className="p-6">
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<h2 className="text-xl font-semibold">Profile Information</h2>
<p className="text-sm text-gray-600">
Update your account's profile information and email address
</p>
</div>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="Your name" />
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="you@example.com" />
</div>
<div className="space-y-2">
<Label htmlFor="bio">Bio</Label>
<textarea
id="bio"
rows={4}
className="w-full rounded-md border border-gray-300 p-3"
placeholder="Tell us about yourself"
/>
</div>
</div>
<div className="flex justify-end">
<Button type="submit" isLoading={isSaving}>
Save Changes
</Button>
</div>
</form>
</Card>
</div>
);
}
CRUD Page (List/Create/Edit/Delete)
// app/users/page.tsx
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Table } from "@/components/ui/table";
import { CreateUserModal } from "@/components/users/CreateUserModal";
import { DeleteConfirmDialog } from "@/components/ui/DeleteConfirmDialog";
export default function UsersPage() {
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">Users</h1>
<p className="text-gray-600">Manage your team members</p>
</div>
<Button onClick={() => setIsCreateModalOpen(true)}>Add User</Button>
</div>
{/* Filters */}
<div className="flex gap-4">
<Input
placeholder="Search users..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="max-w-sm"
/>
</div>
{/* Table */}
<div className="rounded-lg border">
{/* TODO: Implement table with data */}
</div>
{/* Modals */}
<CreateUserModal
isOpen={isCreateModalOpen}
onClose={() => setIsCreateModalOpen(false)}
/>
</div>
);
}
Navigation Components
Sidebar Navigation
// components/Sidebar.tsx
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import {
HomeIcon,
UsersIcon,
SettingsIcon,
ChartBarIcon,
} from "@/components/icons";
const navigation = [
{ name: "Dashboard", href: "/dashboard", icon: HomeIcon },
{ name: "Users", href: "/users", icon: UsersIcon },
{ name: "Analytics", href: "/analytics", icon: ChartBarIcon },
{ name: "Settings", href: "/settings", icon: SettingsIcon },
];
export function Sidebar({ className }: { className?: string }) {
const pathname = usePathname();
return (
<aside className={cn("border-r bg-white", className)}>
<div className="flex h-16 items-center px-6">
<span className="text-xl font-bold">AppName</span>
</div>
<nav className="space-y-1 px-3">
{navigation.map((item) => {
const isActive = pathname === item.href;
return (
<Link
key={item.name}
href={item.href}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
isActive
? "bg-primary-50 text-primary-600"
: "text-gray-700 hover:bg-gray-100"
)}
>
<item.icon className="h-5 w-5" />
{item.name}
</Link>
);
})}
</nav>
</aside>
);
}
Header with User Menu
// components/Header.tsx
"use client";
import { Button } from "@/components/ui/button";
import { Avatar } from "@/components/ui/avatar";
import { DropdownMenu } from "@/components/ui/dropdown-menu";
import { BellIcon, MenuIcon } from "@/components/icons";
export function Header() {
return (
<header className="flex h-16 items-center justify-between border-b bg-white px-4 md:px-6">
{/* Mobile menu button */}
<Button variant="ghost" size="icon" className="lg:hidden">
<MenuIcon className="h-6 w-6" />
</Button>
{/* Search (optional) */}
<div className="flex-1 px-4">{/* Search component */}</div>
{/* Right side actions */}
<div className="flex items-center gap-4">
<Button variant="ghost" size="icon">
<BellIcon className="h-5 w-5" />
</Button>
<DropdownMenu>
<Avatar src="/avatar.jpg" alt="User" />
</DropdownMenu>
</div>
</header>
);
}
Routing Structure
Next.js App Router
app/
├── (auth)/ # Auth group (no dashboard layout)
│ ├── layout.tsx
│ ├── login/
│ │ └── page.tsx
│ ├── register/
│ │ └── page.tsx
│ └── forgot-password/
│ └── page.tsx
├── dashboard/ # Dashboard section
│ ├── layout.tsx
│ └── page.tsx
├── users/ # CRUD section
│ ├── page.tsx # List
│ ├── [id]/
│ │ ├── page.tsx # Detail/Edit
│ │ └── loading.tsx
│ └── new/
│ └── page.tsx # Create
├── settings/ # Settings section
│ ├── layout.tsx
│ ├── profile/
│ │ └── page.tsx
│ ├── security/
│ │ └── page.tsx
│ └── notifications/
│ └── page.tsx
└── layout.tsx # Root layout
State Management Patterns
Data Fetching Pattern
// app/users/page.tsx
import { Suspense } from "react";
import { UsersTable } from "@/components/users/UsersTable";
import { UsersTableSkeleton } from "@/components/users/UsersTableSkeleton";
async function getUsers() {
// Server-side data fetching
const res = await fetch("https://api.example.com/users", {
cache: "no-store",
});
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
return (
<div className="space-y-6">
<h1 className="text-3xl font-bold">Users</h1>
<Suspense fallback={<UsersTableSkeleton />}>
<UsersTable data={users} />
</Suspense>
</div>
);
}
Client-Side State
"use client";
import { useState, useEffect } from "react";
import { useUsers } from "@/hooks/useUsers";
export default function UsersPage() {
const { users, isLoading, error } = useUsers();
const [selectedUser, setSelectedUser] = useState(null);
if (isLoading) return <LoadingState />;
if (error) return <ErrorState error={error} />;
return <div>{/* Page content */}</div>;
}
Loading States
Skeleton Screens
// components/dashboard/DashboardSkeleton.tsx
export function DashboardSkeleton() {
return (
<div className="space-y-6 animate-pulse">
<div className="h-8 w-48 bg-gray-200 rounded" />
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="h-32 bg-gray-200 rounded-lg" />
))}
</div>
<div className="grid gap-6 md:grid-cols-2">
<div className="h-64 bg-gray-200 rounded-lg" />
<div className="h-64 bg-gray-200 rounded-lg" />
</div>
</div>
);
}
Loading Component
// app/dashboard/loading.tsx
import { DashboardSkeleton } from "@/components/dashboard/DashboardSkeleton";
export default function Loading() {
return <DashboardSkeleton />;
}
Responsive Patterns
Mobile Navigation
"use client";
import { useState } from "react";
import { Sheet } from "@/components/ui/sheet";
export function MobileNav() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)} className="lg:hidden">
<MenuIcon />
</button>
<Sheet isOpen={isOpen} onClose={() => setIsOpen(false)}>
<nav className="space-y-2 p-4">{/* Navigation items */}</nav>
</Sheet>
</>
);
}
Best Practices
- Consistent layouts: Use layout files for shared structure
- Route groups: Organize related pages with (groupName)
- Loading states: Add loading.tsx for automatic suspense
- Error boundaries: Add error.tsx for error handling
- Mobile-first: Design for mobile, enhance for desktop
- Accessibility: Semantic HTML, ARIA labels, keyboard nav
- SEO: Use metadata, proper heading hierarchy
- Performance: Code splitting, lazy loading, optimized images
Output Checklist
Every page layout should include:
- Proper route structure with layout files
- Responsive navigation (sidebar + mobile menu)
- Header with actions
- Main content area with proper spacing
- Loading states (skeletons)
- Empty states
- Error boundaries
- State management placeholders
- Breadcrumbs or page headers
- Mobile-responsive breakpoints
Weekly Installs
11
Repository
patricio0312rev/skillsFirst Seen
10 days ago
Installed on
claude-code8
gemini-cli7
antigravity7
windsurf7
github-copilot7
codex7