From atomic-design
Generates Atomic Design templates for skeletal page layouts without real content, composing organisms into responsive structures. Includes React/TypeScript/CSS examples for marketing, app, content, and e-commerce pages.
npx claudepluginhub thebushidocollective/han --plugin atomic-designThis skill is limited to using the following tools:
Master the creation of templates - page-level layouts that define content structure without actual content. Templates establish the skeletal structure that pages will use.
Guides design of template aggregation (gallery/hub) and detail pages for programmatic SEO strategies and user-facing template marketplaces in CMS, design tools, website builders, and vibe coding.
Generates reusable UI patterns like card sections, grids, lists, forms, and chart wrappers from StyleSeed Toss primitives for consistent layouts across pages.
Generates standalone HTML components and composes them into branded full pages from design systems, with embedded CSS, responsive design, and brand integration. Invoke via /html-page or /html-page-quick.
Share bugs, ideas, or general feedback.
Master the creation of templates - page-level layouts that define content structure without actual content. Templates establish the skeletal structure that pages will use.
Templates are the page-level objects that place components into a layout and articulate the design's underlying content structure. They are:
// 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<MainLayoutProps> = ({
headerProps,
footerProps,
children,
showBreadcrumbs = false,
breadcrumbs,
maxWidth = 'lg',
background = 'white',
}) => {
return (
<div className={`${styles.layout} ${styles[`bg-${background}`]}`}>
<Header {...headerProps} />
<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>
);
};
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;
}
}
// 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<DashboardLayoutProps> = ({
headerProps,
sidebarProps,
children,
pageTitle,
pageDescription,
pageActions,
sidebarCollapsed = false,
}) => {
const [isCollapsed, setIsCollapsed] = useState(sidebarCollapsed);
return (
<div className={styles.layout}>
{/* Top Header */}
<Header
logo={headerProps.logo}
navigation={[]}
user={headerProps.user}
onLogout={headerProps.onLogout}
showSearch={false}
/>
<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>
);
};
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;
}
}
// 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<AuthLayoutProps> = ({
logo,
title,
subtitle,
children,
footer,
backgroundImage,
showSidePanel = false,
sidePanelContent,
}) => {
return (
<div className={styles.layout}>
{/* Side Panel (optional) */}
{showSidePanel && (
<div
className={styles.sidePanel}
style={
backgroundImage
? { backgroundImage: `url(${backgroundImage})` }
: undefined
}
>
<div className={styles.sidePanelContent}>{sidePanelContent}</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>
);
};
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;
}
// 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<MainLayoutProps, 'children'>;
/** Category title */
categoryTitle: string;
/** Category description */
categoryDescription?: string;
/** Product count */
productCount: number;
/** Filter sidebar content */
filters: React.ReactNode;
/** Sort/view controls */
controls: React.ReactNode;
/** Product grid content */
products: React.ReactNode;
/** Pagination content */
pagination?: React.ReactNode;
/** Show filters on mobile */
mobileFiltersOpen?: boolean;
/** Toggle mobile filters */
onToggleMobileFilters?: () => void;
}
export const ProductListingLayout: React.FC<ProductListingLayoutProps> = ({
layoutProps,
categoryTitle,
categoryDescription,
productCount,
filters,
controls,
products,
pagination,
mobileFiltersOpen = false,
onToggleMobileFilters,
}) => {
return (
<MainLayout {...layoutProps} maxWidth="xl">
{/* Category Header */}
<header className={styles.header}>
<div className={styles.titleSection}>
<h1 className={styles.title}>{categoryTitle}</h1>
{categoryDescription && (
<p className={styles.description}>{categoryDescription}</p>
)}
<span className={styles.count}>{productCount} products</span>
</div>
</header>
<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>
);
};
ProductListingLayout.displayName = 'ProductListingLayout';
// 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<MainLayoutProps, 'children'>;
/** Post title */
title: string;
/** Post subtitle */
subtitle?: string;
/** Featured image */
featuredImage?: string;
/** Author information */
author: Author;
/** Publication date */
publishedAt: string;
/** Reading time */
readingTime?: string;
/** Post categories/tags */
tags?: React.ReactNode;
/** Main article content */
children: React.ReactNode;
/** Table of contents */
tableOfContents?: React.ReactNode;
/** Author bio card */
showAuthorBio?: boolean;
/** Related posts */
relatedPosts?: React.ReactNode;
/** Comments section */
comments?: React.ReactNode;
/** Social share buttons */
shareButtons?: React.ReactNode;
}
export const BlogPostLayout: React.FC<BlogPostLayoutProps> = ({
layoutProps,
title,
subtitle,
featuredImage,
author,
publishedAt,
readingTime,
tags,
children,
tableOfContents,
showAuthorBio = true,
relatedPosts,
comments,
shareButtons,
}) => {
const formattedDate = new Date(publishedAt).toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
});
return (
<MainLayout {...layoutProps} maxWidth="md">
<article className={styles.article}>
{/* Article Header */}
<header className={styles.header}>
{tags && <div className={styles.tags}>{tags}</div>}
<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>
);
};
BlogPostLayout.displayName = 'BlogPostLayout';
// 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<TwoColumnLayoutProps> = ({
main,
sidebar,
sidebarPosition = 'right',
sidebarWidth = 'medium',
stickySidebar = false,
reverseMobile = false,
gap = 'md',
}) => {
const layoutClass = [
styles.layout,
styles[`sidebar-${sidebarPosition}`],
styles[`width-${sidebarWidth}`],
styles[`gap-${gap}`],
reverseMobile && styles.reverseMobile,
]
.filter(Boolean)
.join(' ');
const sidebarClass = [
styles.sidebar,
stickySidebar && styles.sticky,
]
.filter(Boolean)
.join(' ');
return (
<div className={layoutClass}>
<main className={styles.main}>{main}</main>
<aside className={sidebarClass}>{sidebar}</aside>
</div>
);
};
TwoColumnLayout.displayName = 'TwoColumnLayout';
// 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
}) => (
<div>
<section>{productGallery}</section>
<section>{productInfo}</section>
<section>{productTabs}</section>
<section>{relatedProducts}</section>
</div>
);
// BAD: Template with hardcoded content
const ProductDetailLayout = ({ product }) => (
<div>
<ProductGallery images={product.images} /> {/* Too specific */}
<h1>{product.name}</h1> {/* Real content */}
<p>{product.description}</p>
</div>
);
// GOOD: Clear, named content slots
interface PageTemplateProps {
header: React.ReactNode;
hero?: React.ReactNode;
main: React.ReactNode;
sidebar?: React.ReactNode;
footer: React.ReactNode;
}
// BAD: Generic children only
interface PageTemplateProps {
children: React.ReactNode;
}
// GOOD: Responsive considerations built-in
const DashboardLayout = ({ sidebar, main }) => (
<div className={styles.layout}>
<aside className={styles.sidebar}>{sidebar}</aside>
<main className={styles.main}>{main}</main>
</div>
);
// CSS handles responsive behavior
// .sidebar { @media (max-width: 768px) { display: none; } }
// GOOD: Template just arranges organisms
const MainLayout = ({ header, main, footer }) => (
<div className={styles.layout}>
<div className={styles.header}>{header}</div>
<div className={styles.main}>{main}</div>
<div className={styles.footer}>{footer}</div>
</div>
);
// BAD: Template with business logic
const MainLayout = ({ userId }) => {
const user = useUser(userId); // Fetching data
const isAdmin = user?.role === 'admin'; // Business logic
return (
<div>
<Header user={user} showAdmin={isAdmin} />
{/* ... */}
</div>
);
};
// BAD: Hardcoded real content
const HomepageLayout = () => (
<div>
<h1>Welcome to Our Store</h1> {/* Real content! */}
<p>Shop our latest collection...</p> {/* Real content! */}
</div>
);
// GOOD: Content passed as props/children
const HomepageLayout = ({ heroTitle, heroDescription }) => (
<div>
<h1>{heroTitle}</h1>
<p>{heroDescription}</p>
</div>
);
// BAD: Templates containing templates
const AppLayout = () => (
<BaseLayout>
<AuthLayout>
<DashboardLayout>
{/* Too much nesting */}
</DashboardLayout>
</AuthLayout>
</BaseLayout>
);
// GOOD: Choose appropriate template directly
const AppPage = () => (
<DashboardLayout>
{/* Content */}
</DashboardLayout>
);
// BAD: Too many configuration options
interface LayoutProps {
showHeader: boolean;
showFooter: boolean;
showSidebar: boolean;
sidebarPosition: 'left' | 'right';
headerVariant: 'default' | 'minimal' | 'transparent';
footerVariant: 'default' | 'minimal';
maxWidth: 'sm' | 'md' | 'lg' | 'xl';
// ... 20 more props
}
// GOOD: Create separate templates
const FullPageLayout = ({ ... }) => { ... };
const MinimalLayout = ({ ... }) => { ... };
const SidebarLayout = ({ ... }) => { ... };
// Base layout for all pages
const BaseLayout = ({ children }) => (
<div className="base">
<SkipLink />
{children}
</div>
);
// Marketing layout extending base
const MarketingLayout = ({ children }) => (
<BaseLayout>
<MarketingHeader />
<main>{children}</main>
<MarketingFooter />
</BaseLayout>
);
// App layout extending base
const AppLayout = ({ children }) => (
<BaseLayout>
<AppHeader />
<main>{children}</main>
</BaseLayout>
);
interface SlotLayoutProps {
slots: {
header?: React.ReactNode;
sidebar?: React.ReactNode;
main: React.ReactNode;
footer?: React.ReactNode;
};
}
const SlotLayout: React.FC<SlotLayoutProps> = ({ slots }) => (
<div className={styles.layout}>
{slots.header && <header>{slots.header}</header>}
<div className={styles.body}>
{slots.sidebar && <aside>{slots.sidebar}</aside>}
<main>{slots.main}</main>
</div>
{slots.footer && <footer>{slots.footer}</footer>}
</div>
);
atomic-design-fundamentals - Core methodology overviewatomic-design-organisms - Building complex organismsatomic-design-integration - Framework-specific patterns