Appearance
PageLayout
Purpose
PageLayout combines PageHeader with AsyncContent for consistent page structure:
- Page title and subtitle
- Optional back button
- Action buttons area
- Automatic loading, error, and empty state handling
When to use
Use PageLayout for:
- List pages with async data
- Detail pages with header and content
- Any page that needs consistent header + content structure
Basic usage
With async data
tsx
import { PageLayout } from '@/shared/ui/components/PageLayout'
import { FolderKanban, Plus } from 'lucide-react'
export function BoardsPage() {
const { data: boards, isLoading, error } = useBoards()
return (
<PageLayout
title="Boards"
subtitle="Manage your kanban boards"
actions={<Button onClick={handleCreate}><Plus /> New Board</Button>}
isLoading={isLoading}
error={error}
data={boards}
loadingFallback={<BoardsSkeleton />}
errorConfig={{
title: 'Failed to load boards',
icon: FolderKanban,
onRetry: refetch,
}}
emptyConfig={{
title: 'No boards yet',
description: 'Create your first board to get started',
action: { label: 'Create Board', onClick: handleCreate, icon: Plus },
}}
>
{(boards) => <BoardList boards={boards} />}
</PageLayout>
)
}Static content (no async)
tsx
import { PageLayout } from '@/shared/ui/components/PageLayout'
export function SettingsPage() {
return (
<PageLayout
title="Settings"
subtitle="Manage your preferences"
backButton={{ to: '/dashboard', label: 'Back' }}
>
<SettingsForm />
</PageLayout>
)
}Props
Base props (always available)
| Prop | Type | Description |
|---|---|---|
title | string | ReactNode | Page title |
subtitle | string | ReactNode | Optional subtitle |
backButton | { to?: string, label?: string, onClick?: () => void } | Back navigation |
actions | ReactNode | Action buttons (right side) |
className | string | Container class |
contentClassName | string | Content area class |
gap | 'none' | 'sm' | 'md' | 'lg' | Gap between header and content |
Async props (for data-driven pages)
| Prop | Type | Description |
|---|---|---|
isLoading | boolean | Loading state |
error | Error | null | Error object |
data | T | The data to render |
isEmpty | boolean | ((data: T) => boolean) | Custom empty check |
loadingFallback | ReactNode | Loading skeleton |
errorConfig | ErrorConfig | Error state configuration |
emptyConfig | EmptyConfig | Empty state configuration |
children | (data: T) => ReactNode | Render function |
ErrorConfig
| Property | Type | Description |
|---|---|---|
title | string | Error title |
description | string | Error description |
icon | LucideIcon | Custom icon |
onRetry | () => void | Retry callback |
retryText | string | Retry button text |
EmptyConfig
| Property | Type | Description |
|---|---|---|
title | string | Empty state title |
description | string | Empty state description |
icon | LucideIcon | Custom icon |
action | { label, onClick, icon? } | Primary action button |
Notes
- For async usage,
childrenmust be a render function - For static usage,
childrenis rendered directly - The header is always visible, even during loading/error states
- Uses AsyncContent internally for state handling