Atomic Design: Templates
Master the creation of templates - page-level layouts that define content structure without actual content. Templates establish the skeletal structure that pages will use.
What Are Templates?
Templates are the page-level objects that place components into a layout and articulate the design's underlying content structure. They are:
Composed of organisms: Arrange organisms into page layouts Content-agnostic: Use placeholder content, not real data Structural: Define where content types will appear Reusable: Same template can be used by multiple pages Responsive: Handle all viewport sizes Common Template Types Marketing Templates Landing page layouts Homepage layouts Product showcase layouts About/Company layouts Application Templates Dashboard layouts Settings page layouts Profile page layouts List/Detail page layouts Content Templates Blog post layouts Article layouts Documentation layouts Help center layouts E-commerce Templates Product listing layouts Product detail layouts Checkout layouts Order confirmation layouts MainLayout Template Example Complete Implementation // templates/MainLayout/MainLayout.tsx import React from 'react'; import { Header, type HeaderProps } from '@/components/organisms/Header'; import { Footer, type FooterProps } from '@/components/organisms/Footer'; import styles from './MainLayout.module.css';
export interface MainLayoutProps { / Header configuration */ headerProps: HeaderProps; / Footer configuration / footerProps: FooterProps; / Main content / children: React.ReactNode; / Show breadcrumbs */ showBreadcrumbs?: boolean; / Breadcrumb component / breadcrumbs?: React.ReactNode; / Maximum content width / maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | 'full'; /* Page background color / background?: 'white' | 'gray' | 'primary'; }
export const MainLayout: React.FC );
}; MainLayout.displayName = 'MainLayout'; / templates/MainLayout/MainLayout.module.css /
.layout {
display: flex;
flex-direction: column;
min-height: 100vh;
} .main {
flex: 1;
display: flex;
flex-direction: column;
} .breadcrumbs {
padding: 16px 24px;
background-color: var(--color-neutral-50);
border-bottom: 1px solid var(--color-neutral-200);
} .content {
flex: 1;
margin: 0 auto;
padding: 24px;
width: 100%;
} / Max Width Variants /
.max-sm {
max-width: 640px;
} .max-md {
max-width: 768px;
} .max-lg {
max-width: 1024px;
} .max-xl {
max-width: 1280px;
} .max-full {
max-width: 100%;
} / Background Variants /
.bg-white {
background-color: var(--color-white);
} .bg-gray {
background-color: var(--color-neutral-50);
} .bg-primary {
background-color: var(--color-primary-50);
} / Responsive adjustments /
@media (max-width: 768px) {
.content {
padding: 16px;
}
} DashboardLayout Template Example
// templates/DashboardLayout/DashboardLayout.tsx
import React, { useState } from 'react';
import { Header } from '@/components/organisms/Header';
import { Sidebar, type SidebarProps } from '@/components/organisms/Sidebar';
import styles from './DashboardLayout.module.css'; export interface DashboardLayoutProps {
/ Header props */
headerProps: {
logo: React.ReactNode;
user?: { name: string; email: string; avatar?: string };
onLogout?: () => void;
};
/ Sidebar props /
sidebarProps: SidebarProps;
/ Main content /
children: React.ReactNode;
/ Page title */
pageTitle?: string;
/ Page description /
pageDescription?: string;
/ Page actions (buttons, etc.) /
pageActions?: React.ReactNode;
/* Sidebar initially collapsed /
sidebarCollapsed?: boolean;
} export const DashboardLayout: React.FC return (
);
}; DashboardLayout.displayName = 'DashboardLayout'; / templates/DashboardLayout/DashboardLayout.module.css /
.layout {
display: flex;
flex-direction: column;
min-height: 100vh;
} .body {
display: flex;
flex: 1;
} .main {
flex: 1;
display: flex;
flex-direction: column;
overflow-x: hidden;
background-color: var(--color-neutral-50);
} .pageHeader {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 24px;
padding: 24px;
background-color: var(--color-white);
border-bottom: 1px solid var(--color-neutral-200);
} .titleSection {
flex: 1;
} .pageTitle {
margin: 0;
font-size: 24px;
font-weight: 600;
color: var(--color-neutral-900);
} .pageDescription {
margin: 4px 0 0;
font-size: 14px;
color: var(--color-neutral-500);
} .pageActions {
display: flex;
gap: 12px;
flex-shrink: 0;
} .content {
flex: 1;
padding: 24px;
overflow-y: auto;
} @media (max-width: 768px) {
.pageHeader {
flex-direction: column;
align-items: stretch;
} .pageActions {
margin-top: 16px;
} .content {
padding: 16px;
}
} AuthLayout Template Example
// templates/AuthLayout/AuthLayout.tsx
import React from 'react';
import styles from './AuthLayout.module.css'; export interface AuthLayoutProps {
/ Logo element */
logo: React.ReactNode;
/ Page title /
title: string;
/ Page subtitle /
subtitle?: string;
/ Form content */
children: React.ReactNode;
/ Footer content (links, etc.) /
footer?: React.ReactNode;
/ Background image URL /
backgroundImage?: string;
/ Show decorative side panel */
showSidePanel?: boolean;
/ Side panel content */
sidePanelContent?: React.ReactNode;
} export const AuthLayout: React.FC );
}; AuthLayout.displayName = 'AuthLayout'; / templates/AuthLayout/AuthLayout.module.css /
.layout {
display: flex;
min-height: 100vh;
} .sidePanel {
display: none;
width: 50%;
background-color: var(--color-primary-600);
background-size: cover;
background-position: center;
position: relative;
} @media (min-width: 1024px) {
.sidePanel {
display: flex;
align-items: center;
justify-content: center;
}
} .sidePanelContent {
padding: 48px;
color: var(--color-white);
text-align: center;
} .main {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background-color: var(--color-neutral-50);
} .container {
width: 100%;
max-width: 400px;
} .logo {
text-align: center;
margin-bottom: 32px;
} .header {
text-align: center;
margin-bottom: 32px;
} .title {
margin: 0;
font-size: 28px;
font-weight: 700;
color: var(--color-neutral-900);
} .subtitle {
margin: 8px 0 0;
font-size: 16px;
color: var(--color-neutral-500);
} .content {
background-color: var(--color-white);
padding: 32px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
} .footer {
margin-top: 24px;
text-align: center;
font-size: 14px;
color: var(--color-neutral-500);
} .footer a {
color: var(--color-primary-500);
text-decoration: none;
} .footer a:hover {
text-decoration: underline;
} ProductListingLayout Template Example
// templates/ProductListingLayout/ProductListingLayout.tsx
import React from 'react';
import { MainLayout, type MainLayoutProps } from '../MainLayout';
import styles from './ProductListingLayout.module.css'; export interface ProductListingLayoutProps {
/ Main layout props */
layoutProps: Omit export const ProductListingLayout: React.FC {categoryDescription} );
}; ProductListingLayout.displayName = 'ProductListingLayout'; BlogPostLayout Template Example
// templates/BlogPostLayout/BlogPostLayout.tsx
import React from 'react';
import { MainLayout, type MainLayoutProps } from '../MainLayout';
import { Avatar } from '@/components/atoms/Avatar';
import { Text } from '@/components/atoms/Typography';
import styles from './BlogPostLayout.module.css'; export interface Author {
name: string;
avatar?: string;
bio?: string;
} export interface BlogPostLayoutProps {
/ Main layout props */
layoutProps: Omit export const BlogPostLayout: React.FC return (
);
}; BlogPostLayout.displayName = 'BlogPostLayout'; TwoColumnLayout Template Example
// templates/TwoColumnLayout/TwoColumnLayout.tsx
import React from 'react';
import styles from './TwoColumnLayout.module.css'; export interface TwoColumnLayoutProps {
/ Left column (usually main content) */
main: React.ReactNode;
/ Right column (usually sidebar) /
sidebar: React.ReactNode;
/ Sidebar position /
sidebarPosition?: 'left' | 'right';
/ Sidebar width */
sidebarWidth?: 'narrow' | 'medium' | 'wide';
/ Sticky sidebar /
stickySidebar?: boolean;
/ Reverse on mobile (show sidebar first) /
reverseMobile?: boolean;
/* Gap between columns /
gap?: 'sm' | 'md' | 'lg';
} export const TwoColumnLayout: React.FC const sidebarClass = [
styles.sidebar,
stickySidebar && styles.sticky,
]
.filter(Boolean)
.join(' '); return (
TwoColumnLayout.displayName = 'TwoColumnLayout'; Best Practices
1. Use Placeholder Content
// GOOD: Template with placeholder content slots
const ProductDetailLayout = ({
productGallery, // Placeholder for gallery component
productInfo, // Placeholder for product details
productTabs, // Placeholder for tabs
relatedProducts, // Placeholder for recommendations
}) => (
); // BAD: Template with hardcoded content
const ProductDetailLayout = ({ product }) => (
{product.description} ); // BAD: Generic children only
interface PageTemplateProps {
children: React.ReactNode;
} ); // CSS handles responsive behavior
// .sidebar { @media (max-width: 768px) { display: none; } } ); // BAD: Template with business logic
const MainLayout = ({ userId }) => {
const user = useUser(userId); // Fetching data
const isAdmin = user?.role === 'admin'; // Business logic return (
Anti-Patterns to Avoid
1. Templates with Real Content
// BAD: Hardcoded real content
const HomepageLayout = () => (
Shop our latest collection... ); // GOOD: Content passed as props/children
const HomepageLayout = ({ heroTitle, heroDescription }) => (
{heroDescription} ); // GOOD: Choose appropriate template directly
const AppPage = () => (
// GOOD: Create separate templates
const FullPageLayout = ({ ... }) => { ... };
const MinimalLayout = ({ ... }) => { ... };
const SidebarLayout = ({ ... }) => { ... }; Template Composition Patterns
Nested Layouts
// Base layout for all pages
const BaseLayout = ({ children }) => (
); // Marketing layout extending base
const MarketingLayout = ({ children }) => (
// App layout extending base
const AppLayout = ({ children }) => (
Slot-Based Layouts
interface SlotLayoutProps {
slots: {
header?: React.ReactNode;
sidebar?: React.ReactNode;
main: React.ReactNode;
footer?: React.ReactNode;
};
} const SlotLayout: React.FC ); When to Use This Skill
Creating page structure patterns
Building reusable layout components
Establishing consistent page architectures
Setting up responsive frameworks
Defining content slot patterns
Related Skills
atomic-design-fundamentals - Core methodology overview
atomic-design-organisms - Building complex organisms
atomic-design-integration - Framework-specific patterns]}}>
<main className={styles.main}>
{showBreadcrumbs && breadcrumbs && (
<div className={styles.breadcrumbs}>{breadcrumbs}</div>
)}
<div className={`${styles.content} ${styles[`max-${maxWidth}`]}`}>
{children}
</div>
</main>
<Footer {...footerProps} />
</div>
<div className={styles.body}>
{/* Sidebar */}
<Sidebar
{...sidebarProps}
isCollapsed={isCollapsed}
onToggleCollapse={() => setIsCollapsed(!isCollapsed)}
/>
{/* Main Content Area */}
<main className={styles.main}>
{/* Page Header */}
{(pageTitle || pageActions) && (
<header className={styles.pageHeader}>
<div className={styles.titleSection}>
{pageTitle && <h1 className={styles.pageTitle}>{pageTitle}</h1>}
{pageDescription && (
<p className={styles.pageDescription}>{pageDescription}</p>
)}
</div>
{pageActions && (
<div className={styles.pageActions}>{pageActions}</div>
)}
</header>
)}
{/* Page Content */}
<div className={styles.content}>{children}</div>
</main>
</div>
</div>
{/* Main Content */}
<div className={styles.main}>
<div className={styles.container}>
{/* Logo */}
<div className={styles.logo}>{logo}</div>
{/* Header */}
<header className={styles.header}>
<h1 className={styles.title}>{title}</h1>
{subtitle && <p className={styles.subtitle}>{subtitle}</p>}
</header>
{/* Form Content */}
<div className={styles.content}>{children}</div>
{/* Footer */}
{footer && <footer className={styles.footer}>{footer}</footer>}
</div>
</div>
</div>
{categoryTitle}
{categoryDescription && (
<div className={styles.body}>
{/* Desktop Filters */}
<aside className={styles.sidebar}>
<div className={styles.sidebarContent}>{filters}</div>
</aside>
{/* Mobile Filters Overlay */}
{mobileFiltersOpen && (
<div className={styles.mobileFilters}>
<div className={styles.mobileFiltersHeader}>
<h2>Filters</h2>
<button onClick={onToggleMobileFilters}>Close</button>
</div>
<div className={styles.mobileFiltersContent}>{filters}</div>
</div>
)}
{/* Main Content */}
<div className={styles.main}>
{/* Controls Bar */}
<div className={styles.controls}>
<button
className={styles.mobileFilterButton}
onClick={onToggleMobileFilters}
>
Filters
</button>
{controls}
</div>
{/* Product Grid */}
<div className={styles.products}>{products}</div>
{/* Pagination */}
{pagination && (
<div className={styles.pagination}>{pagination}</div>
)}
</div>
</div>
</MainLayout>
<h1 className={styles.title}>{title}</h1>
{subtitle && <p className={styles.subtitle}>{subtitle}</p>}
{/* Author & Meta */}
<div className={styles.meta}>
<div className={styles.author}>
<Avatar
src={author.avatar}
alt={author.name}
initials={author.name.slice(0, 2).toUpperCase()}
size="md"
/>
<div className={styles.authorInfo}>
<Text weight="semibold">{author.name}</Text>
<Text size="sm" color="muted">
{formattedDate}
{readingTime && ` · ${readingTime}`}
</Text>
</div>
</div>
{shareButtons && (
<div className={styles.share}>{shareButtons}</div>
)}
</div>
</header>
{/* Featured Image */}
{featuredImage && (
<figure className={styles.featuredImage}>
<img src={featuredImage} alt={title} />
</figure>
)}
{/* Content with Optional TOC */}
<div className={styles.contentWrapper}>
{/* Table of Contents (Desktop) */}
{tableOfContents && (
<aside className={styles.toc}>
<div className={styles.tocContent}>{tableOfContents}</div>
</aside>
)}
{/* Main Content */}
<div className={styles.content}>{children}</div>
</div>
{/* Author Bio */}
{showAuthorBio && (
<footer className={styles.authorBio}>
<Avatar
src={author.avatar}
alt={author.name}
initials={author.name.slice(0, 2).toUpperCase()}
size="lg"
/>
<div>
<Text weight="semibold" size="lg">
{author.name}
</Text>
{author.bio && <Text color="muted">{author.bio}</Text>}
</div>
</footer>
)}
{/* Share (Bottom) */}
{shareButtons && (
<div className={styles.bottomShare}>{shareButtons}</div>
)}
</article>
{/* Related Posts */}
{relatedPosts && (
<section className={styles.relatedPosts}>
<h2>Related Posts</h2>
{relatedPosts}
</section>
)}
{/* Comments */}
{comments && (
<section className={styles.comments}>{comments}</section>
)}
</MainLayout>
sidebar-${sidebarPosition}],
styles[width-${sidebarWidth}],
styles[gap-${gap}],
reverseMobile && styles.reverseMobile,
]
.filter(Boolean)
.join(' ');
{product.name}
{/* Real content */}
Welcome to Our Store
{/* Real content! */}
{heroTitle}