10 KiB
Atomic Component Library
This codebase follows the Atomic Design methodology to organize UI components into a scalable, maintainable structure.
Structure Overview
src/components/
├── atoms/ # Basic building blocks (smallest components)
├── molecules/ # Simple combinations of atoms
├── organisms/ # Complex components built from molecules and atoms
├── ui/ # shadcn base components
└── [features]/ # Feature-specific complex components
Hierarchy Explained
🧪 Atoms (/atoms)
Purpose: The smallest, most fundamental UI elements that cannot be broken down further.
Characteristics:
- Single-purpose components
- No business logic
- Highly reusable
- Accept minimal props
- No dependencies on other custom components (may use shadcn)
Examples:
AppLogo- The application logo iconTabIcon- Icon wrapper with styling variantsStatusIcon- Status indicator icons (saved, synced)ErrorBadge- Badge showing error count
When to create an atom:
- You have a single UI element used in multiple places
- The component has no complex logic or state
- It's a styled wrapper around a basic HTML element or icon
🔬 Molecules (/molecules)
Purpose: Simple combinations of atoms that work together as a unit.
Characteristics:
- Composed of 2-5 atoms
- Single responsibility
- Minimal state management
- Reusable across multiple contexts
- May include simple interactions
Examples:
SaveIndicator- Combines StatusIcon + text to show save statusAppBranding- Combines AppLogo + title + subtitlePageHeaderContent- Combines TabIcon + title + descriptionToolbarButton- Button + Tooltip wrapperNavigationItem- Icon + label + badge navigation buttonNavigationGroupHeader- Collapsible group header with count
When to create a molecule:
- You're combining atoms to create a meaningful UI pattern
- The combination is reused in multiple organisms
- It represents a single functional unit (like "branding" or "save status")
🧬 Organisms (/organisms)
Purpose: Complex, feature-rich components that combine molecules and atoms.
Characteristics:
- Complex composition (5+ child components)
- May manage state
- Business logic allowed
- Feature-specific functionality
- Can include API calls or data fetching
Examples:
NavigationMenu- Full sidebar navigation with groups, items, searchPageHeader- Complete page header with icon and descriptionToolbarActions- Toolbar with multiple action buttonsAppHeader- Complete application header with nav, branding, save indicator, and actions
When to create an organism:
- You're building a major UI section (header, navigation, toolbar)
- The component manages complex state or user interactions
- It coordinates multiple molecules and atoms
- It's feature-specific rather than generic
🏗️ Feature Components (/components/[FeatureName].tsx)
Purpose: Full-featured, domain-specific components that implement complete features.
Characteristics:
- High-level business components
- Complete feature implementations
- May use multiple organisms, molecules, and atoms
- Include complex state management
- Feature-specific (not reusable across features)
Examples:
CodeEditor- Full Monaco code editor with file managementModelDesigner- Complete Prisma model design interfaceComponentTreeBuilder- React component hierarchy builderWorkflowDesigner- Visual workflow design canvasProjectDashboard- Complete dashboard view
When to create a feature component:
- You're implementing a complete feature or page
- The component is not reusable outside its feature domain
- It requires significant state management and business logic
Component Organization Rules
1. Import Hierarchy
Components should only import from their level or below:
- ✅ Organisms can import molecules and atoms
- ✅ Molecules can import atoms
- ❌ Atoms should NOT import molecules or organisms
- ❌ Molecules should NOT import organisms
2. Naming Conventions
- Atoms: Simple nouns (
AppLogo,StatusIcon,ErrorBadge) - Molecules: Descriptive combinations (
SaveIndicator,ToolbarButton,NavigationItem) - Organisms: Feature-descriptive (
NavigationMenu,AppHeader,ToolbarActions) - Features: Feature names (
CodeEditor,ModelDesigner,ProjectDashboard)
3. File Structure
Each atomic level has:
atoms/
├── AppLogo.tsx
├── TabIcon.tsx
├── StatusIcon.tsx
├── ErrorBadge.tsx
└── index.ts # Exports all atoms
4. Index Files
Each directory should have an index.ts for clean imports:
// atoms/index.ts
export { AppLogo } from './AppLogo'
export { TabIcon } from './TabIcon'
export { StatusIcon } from './StatusIcon'
export { ErrorBadge } from './ErrorBadge'
This allows:
// Good ✅
import { AppLogo, StatusIcon } from '@/components/atoms'
// Instead of ❌
import { AppLogo } from '@/components/atoms/AppLogo'
import { StatusIcon } from '@/components/atoms/StatusIcon'
Configuration Files
lib/navigation-config.tsx
Centralized configuration for navigation structure:
tabInfo: Maps tab IDs to their display informationnavigationGroups: Defines navigation hierarchy and groupingsNavigationItemData: TypeScript interfaces for type safety
Benefits:
- Single source of truth for navigation
- Easy to add/remove navigation items
- Type-safe navigation configuration
- Separates data from presentation logic
Migration Guide
When refactoring existing components:
-
Identify the component's level
- Does it contain business logic? → Feature component
- Does it combine many elements? → Organism
- Does it combine a few atoms? → Molecule
- Is it a single UI element? → Atom
-
Check dependencies
- What does it import?
- Can it be broken into smaller pieces?
- Are parts reusable elsewhere?
-
Extract reusable parts
// Before: Monolithic component function Header() { return ( <header> <div className="logo">...</div> <div className="save">...</div> <div className="actions">...</div> </header> ) } // After: Atomic structure // atoms/AppLogo.tsx export function AppLogo() { ... } // molecules/SaveIndicator.tsx export function SaveIndicator() { ... } // organisms/AppHeader.tsx export function AppHeader() { return ( <header> <AppLogo /> <SaveIndicator /> <ToolbarActions /> </header> ) } -
Move to appropriate directory
- Create the component file in its level directory
- Update the level's
index.ts - Update imports in consuming components
Best Practices
1. Keep Atoms Pure
// Good ✅ - Pure, reusable atom
export function StatusIcon({ type, size = 14 }: StatusIconProps) {
return type === 'saved'
? <CheckCircle size={size} className="text-accent" />
: <CloudCheck size={size} />
}
// Bad ❌ - Too much logic for an atom
export function StatusIcon({ lastSaved }: { lastSaved: number }) {
const [time, setTime] = useState(Date.now())
useEffect(() => { /* complex logic */ }, [lastSaved])
return <CheckCircle />
}
2. Compose in Molecules
// Good ✅ - Molecule combines atoms with simple logic
export function SaveIndicator({ lastSaved }: SaveIndicatorProps) {
const isRecent = Date.now() - lastSaved < 3000
return (
<div>
<StatusIcon type={isRecent ? 'saved' : 'synced'} />
<span>{isRecent ? 'Saved' : timeAgo}</span>
</div>
)
}
3. Coordinate in Organisms
// Good ✅ - Organism manages complex state and coordinates molecules
export function AppHeader({ onSave, onSearch }: AppHeaderProps) {
const [lastSaved, setLastSaved] = useState<number | null>(null)
return (
<header>
<AppBranding />
<SaveIndicator lastSaved={lastSaved} />
<ToolbarActions onSave={onSave} onSearch={onSearch} />
</header>
)
}
4. Single Responsibility
Each component should do one thing well:
AppLogo→ Show the logoSaveIndicator→ Show save statusAppHeader→ Compose the complete header
5. Props Over Children (Usually)
Prefer explicit props for better type safety:
// Good ✅
<ToolbarButton icon={<Search />} label="Search" onClick={onSearch} />
// Less ideal (but sometimes necessary)
<ToolbarButton onClick={onSearch}>
<Search />
Search
</ToolbarButton>
Benefits of Atomic Design
- Reusability: Atoms and molecules can be used across features
- Consistency: Shared atoms ensure consistent UI
- Testability: Small components are easier to test
- Maintainability: Changes propagate naturally through composition
- Collaboration: Clear boundaries make teamwork easier
- Documentation: Structure itself documents component relationships
- Performance: Smaller components are easier to optimize
- Refactoring: Easy to identify and extract reusable patterns
Quick Reference
| Level | Size | State | Logic | Reusability | Examples |
|---|---|---|---|---|---|
| Atom | 1 element | None | None | Very High | Logo, Icon, Badge |
| Molecule | 2-5 atoms | Minimal | Simple | High | SaveIndicator, ToolbarButton |
| Organism | 5+ components | Moderate | Complex | Medium | Navigation, Header, Toolbar |
| Feature | Full feature | Complex | Business | Low | CodeEditor, Dashboard |
Future Improvements
Potential enhancements to the atomic structure:
- Storybook Integration: Document all atoms and molecules in Storybook
- Visual Regression Testing: Test component appearance automatically
- Component Playground: Interactive component explorer
- Design Tokens: Move colors/spacing to design token system
- Component Generator: CLI tool to scaffold new components
- Dependency Graphs: Visualize component relationships
Getting Help
When deciding where a component belongs, ask:
- Can it be broken down? → If yes, it's not an atom
- Does it combine atoms? → It's at least a molecule
- Does it have complex state? → Probably an organism
- Is it feature-specific? → Keep it as a feature component
Still unsure? Start with a higher level (organism or feature) and refactor down as patterns emerge.