Skip to Content
Module 4: Styling & Layout4.2 Component Design Patterns

Module 4.2: Component Design Patterns

Module 4.2 Banner: Component Design Patterns

🎯 Learning Objectives

After this lesson, you will master:

  • Component Organization: Understand strategies for organizing code folders (Category-based vs. Feature-based).
  • Composition Pattern: Techniques using the children prop to create flexible, customizable components instead of rigid inheritance.
  • CLS Optimization: How to handle images and layout to prevent Cumulative Layout Shift, improving both UX and SEO.

1. Component Organization Patterns

There are many ways to organize folders in a React project. Below are 3 of the most common patterns, ranging from pragmatic to structured.

1.1. Category-based (Organizing by Function)

Diagram illustrating a Category-based folder structure for components

This is a pattern you often see in projects using modern UI libraries (like Shadcn/UI, Mantine). Components are grouped by their role within the entire application.

Folder structure:

src/ components/ ui/ # Basic, atomic components (Button, Input, Card) layouts/ # Common layout frames (Header, Sidebar, Footer) common/ # More complex shared components (LoadingSpinner, ErrorState) icons/ # SVG icons

Pros: Easy to find global/shared components. Cons: Can become hard to manage when complex business logic is intertwined with the UI.

1.2. Feature-based / Colocation (Organizing by Feature)

Diagram illustrating a Feature-based (Colocation) folder structure for modules

“Keep things that change together, close together.” Components that solely serve a specific feature (or a particular page) should reside directly within that feature’s folder, not in a general components folder.

Folder structure:

src/ features/ # Major domains auth/ components/ # Specific to Auth (LoginForm, RegisterForm) hooks/ types/ product/ components/ # Product forms, product lists, etc. pages/ # Page structure (Next.js / Vite) dashboard/ components/ # Widgets exclusive to the dashboard index.tsx

Pros: Highly scalable; deleting a feature often means just deleting a single folder. Cons: Requires team discipline to avoid mixing Common and Feature components.

1.3. Atomic Design (A Design Mindset)

Regardless of your chosen folder structure, Atomic Design remains a foundational mindset to “decompose” your UI.

Diagram illustrating the five levels of Atomic Design: Atoms, Molecules, Organisms, Templates, and Pages

This mindset divides the UI into 5 levels:

  1. Atoms: Smallest units (Button, Input, Label). -> Corresponds to the ui/ folder.
  2. Molecules: Groups of Atoms (SearchBox = Input + Button). -> Corresponds to common/ or features/ folders.
  3. Organisms: Larger groups (Header, ProductList). -> Corresponds to layouts/ or features/ folders.
  4. Templates/Pages: Complete pages.

Atomic Design Mindset Example:

// Atom: Button.tsx interface ButtonProps extends React.PropsWithChildren {} export default function Button({ children }: ButtonProps) { return <button className="bg-blue-500 text-white px-4 py-2 rounded">{children}</button>; } // Molecule: SearchBox.tsx (Combines Atoms) export function SearchBox() { return ( <div className="flex gap-2"> <input className="border p-2" placeholder="Search..." /> <Button>Tìm kiếm</Button> </div> ); }

2. Composition Pattern

React prioritizes Composition over Inheritance. The core technique involves using the special children prop.

2.1. Flexible Containers with children

Instead of passing content via a string prop (content="..."), use children to pass arbitrary JSX.

// ❌ Limited: Only accepts text function Box({ content }: { content: string }) { return <div className="box">{content}</div>; } // ✅ Flexible: Accepts anything (Text, HTML, other Components) interface BoxProps { children: React.ReactNode; } function Box({ children }: BoxProps) { return <div className="border p-4 rounded bg-white shadow">{children}</div>; } // Usage <Box> <h2 className="text-xl">Title</h2> <Button>Action</Button> </Box>;

2.2. Slots Pattern (Multiple Content Areas)

If a component needs multiple distinct content areas (e.g., a Layout with Header, Sidebar, Content), pass components directly as props.

interface AppLayoutProps { header: React.ReactNode; sidebar: React.ReactNode; content: React.ReactNode; } function AppLayout({ header, sidebar, content }: AppLayoutProps) { return ( <div className="min-h-screen"> <div className="top-bar">{header}</div> <div className="flex"> <div className="w-64">{sidebar}</div> <div className="flex-1">{content}</div> </div> </div> ); } // Usage <AppLayout header={<Header />} sidebar={<Nav />} content={<Dashboard />} />;

3. CLS (Cumulative Layout Shift) & Image Handling

CLS is a metric measuring UI stability. A common issue arises when slow-loading images push down content below them, creating a frustrating user experience.

Illustration of Cumulative Layout Shift (CLS) showing content jumping due to image loading

3.1. The Cause

The browser doesn’t know the image dimensions before it finishes loading. It renders with height = 0, then once loaded, renders with height = X px, causing a layout shift.

3.2. The Solution: Set Fixed Dimensions or Aspect Ratio

Method 1: Set specific width/height

// Always set width and height attributes <img src="avatar.jpg" width="100" height="100" alt="User Avatar" className="rounded-full" />

Method 2: Use Aspect Ratio (Tailwind) If the wrapper has a responsive width, use aspect-ratio to reserve vertical space based on its ratio.

<div className="aspect-video bg-gray-200 w-full overflow-hidden rounded-lg"> <img src="banner.jpg" alt="Banner" className="w-full h-full object-cover" loading="lazy" /> </div>

4. Hands-On (Labs)

Lab 1: Universal Modal System

Build an incredibly flexible (reusable) Modal component that works for any situation:

  • Variants: Success (Green), Error (Red), Info (Blue).
  • Slots: Header, Body (children), Footer.
  • Supports closing when clicking outside (overlay click) or pressing Esc (keydown event).

Example of a universal modal component with header, body, and footer slots

💡 Hints:

  • Utilize the Composition pattern: The Modal component accepts children to render custom body content.
  • Props: isOpen (boolean), onClose (function), title (string), variant (enum).
  • Use useEffect to listen for the keydown event (ESC key) and call onClose.
  • Layout: Use Tailwind’s fixed inset-0 for a full-screen overlay.

Lab 2: Pinterest Masonry Layout (Component Organization)

Organize the code for a Pinterest-style “Image Gallery” page.

  • Clearly separate components: PhotoCard (Atom), MasonryGrid (Organism).
  • Handle images to prevent CLS (using aspect-video or setting height).

Example of a Pinterest-style masonry image gallery layout

💡 Hints:

  • Apply a Feature-based folder structure: Create a features/gallery folder.
  • Inside PhotoCard, use an img tag with object-cover and w-full classes.
  • Use CSS columns (columns-3) or a Masonry library for a staggered grid layout.

5. Summary

  1. Component Organization: Choose Category-based (UI/Layouts) for small/medium projects, and Feature-based (Features/Pages) for larger ones. Apply the Atomic mindset to decompose your UI.
  2. Composition: The key to flexibility in React. Use children to pass nested content.
  3. CLS: Always “reserve space” for images using width/height or aspect-ratio to prevent layout shifts, making your app look professional and feel fluid.

6. References

Last updated on