From Atomic Design to Reality:

Shivam ShuklaShivam Shukla
5 min read

When we started building Sarasvishva, we knew the architecture needed to support an extensive component library that would remain maintainable as the project grew. In my previous post on frontend architecture, I discussed our overall approach. Today, I want to dive deeper into how we moved from atomic design theory to practical implementation.

The Promise and Challenge of Atomic Design

Brad Frost's atomic design methodology offers a compelling mental model: build interfaces from the smallest "atoms" up through "molecules," "organisms," "templates," and "pages." But implementing this in a real-world project presents several challenges:

  1. Where do you draw the lines? Is a button with an icon an atom or a molecule?

  2. How do you maintain consistency? With dozens of components, ensuring visual coherence becomes complex.

  3. How do you avoid performance issues? Too much component nesting can create render tree problems.

Here's how we tackled these challenges in Sarasvishva.

Practical Component Organization

Atoms: The Foundation

Rather than debating theoretically what constitutes an "atom," we made a practical decision: atoms are components with no other component dependencies.

Our atomic components include:
// Button atom example const Button = ({ children, variant = 'primary', size = 'medium', ...props }) => { return ( <button className={btn btn-${variant} btn-${size}} {...props} > {children} ); };

By keeping atoms truly primitive, we ensured they remained easy to maintain and test.

Molecules: Purposeful Combinations

We defined molecules as components that combine atoms to create a single interface element with a specific purpose. For example, our search input combines an input atom, a button atom, and an icon atom:
// SearchInput molecule example const SearchInput = ({ placeholder, onSearch, ...props }) => { const [value, setValue] = useState('');

return (

<Input placeholder={placeholder} value={value} onChange={(e) => setValue(e.target.value)} {...props} /> <Button variant="icon" onClick={() => onSearch(value)} aria-label="Search" >

); };

Organisms: Functional Sections

Organisms in Sarasvishva represent complete functional sections of the interface. Our case study section editor is a complex organism composed of multiple molecules:
// Simplified CaseStudyEditor organism example const CaseStudyEditor = ({ initialData, onSave }) => { // State management logic

return (

<ActionBar onSave={() => onSave(data)} />

); };

Naming Conventions That Scale

One challenge with atomic design is maintaining a consistent naming system that makes sense to everyone on the team. We established these rules:

  1. Atoms: Named after their HTML element counterpart when possible (Button, Input, Heading)

  2. Molecules: Named by their functional purpose (SearchInput, MediaCard, ToggleSwitch)

  3. Organisms: Named by their page section or feature (CaseStudyEditor, ProjectGallery, TeamCollaboration)

This naming convention provided immediate context about a component's complexity and purpose.

Directory Structure That Reinforces Hierarchy

Our component structure reinforces the atomic design principles:
/components /atoms /Button Button.jsx Button.test.jsx Button.styles.js /Input /Icon ... /molecules /SearchInput /MediaCard ... /organisms /CaseStudyEditor /ProjectGallery ... /templates /DashboardTemplate /PortfolioTemplate ... /pages /Dashboard /ProjectDetails ...

Each component folder contains all related files (component, tests, styles), which keeps related code together and makes imports cleaner.

Component Props: Consistency Through Design Tokens

To ensure visual consistency across components, we implemented a robust design token system:
// design-tokens.js export const tokens = { colors: { primary: '#3B82F6', secondary: '#10B981', // ...more colors }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', // ...more spacing values }, typography: { fontSizes: { small: '0.875rem', medium: '1rem', large: '1.25rem', // ...more sizes } } };

These tokens flow through our entire component hierarchy, ensuring that a "medium" size means the same thing whether it's on a button, an input, or a heading.

Handling Component Variations

Rather than creating new components for every variation, we implemented a props-based approach for component variations:
<Button variant="primary" size="medium" /> <Button variant="secondary" size="large" /> <Button variant="ghost" size="small" />

This significantly reduced the number of components we needed to maintain while providing flexibility.

Performance Optimizations

Atomic design can lead to deeply nested component trees, which can impact performance. We implemented several optimizations:

  1. Selective React.memo usage: We wrapped frequently used atoms and molecules with React.memo to prevent unnecessary re-renders.

  2. Custom hooks for shared logic: Rather than prop drilling or wrapping everything in context, we extracted shared logic into custom hooks.

  3. Code splitting at the organism level: We implemented dynamic imports for larger organisms to reduce initial bundle size:
    const CaseStudyEditor = React.lazy(() => import('./organisms/CaseStudyEditor'));

    Real-World Example: The Case Study Editor

    To illustrate our approach, let's look at our case study editor component hierarchy:

    • Atoms: Button, Input, TextArea, Heading, Image, Icon

    • Molecules: MediaUploader, RichTextEditor, ColorPicker, ProcessStepCard

    • Organism: CaseStudyEditor (combines all molecules into a functional feature)

    • Template: PortfolioItemTemplate (places CaseStudyEditor within page layout)

    • Page: EditPortfolioItem (adds routing, data fetching, and state management)

This hierarchy allowed us to develop and test each level independently, resulting in more maintainable code.

Lessons Learned

Implementing atomic design taught us several valuable lessons:

  1. Be pragmatic, not dogmatic: Sometimes we bent the rules to improve developer experience or performance.

  2. Document everything: We created a living style guide to showcase all components and their usage patterns.

  3. Test at the right level: We focused unit tests on atoms and molecules, and integration tests on organisms.

  4. Plan for growth: Our initial component set has grown 3x since project inception, but the structure has scaled well.

What's Next

As we continue developing Sarasvishva, we're focusing on enhancing our component system with:

  1. Better accessibility defaults across all atomic components

  2. Animation primitives that compose well with our existing components

  3. Performance metrics to identify and address bottlenecks

I hope this behind-the-scenes look at our implementation of atomic design has been insightful. If you're building your own component library, I'd love to hear your approach - drop a comment below!


Interested in trying Sarasvishva for your portfolio or case studies? Join our waitlist for early access. Have thoughts on our approach to component architecture? Let me know in the comments!

0
Subscribe to my newsletter

Read articles from Shivam Shukla directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shivam Shukla
Shivam Shukla

Hey! I'm developing SarasVishva—a tool to simplify and organize design projects. Support my mission and help bring it to life. Thanks for your support!