Implementing Atomic Design in Next.js Projects
Hello everyone, and thank you for taking the time to read my article. Today, I’ll be sharing how to implement the Atomic Design principle in your Next.js projects. Before we dive into the main discussion, let me briefly explain what Atomic Design is for those who might not be familiar with the concept.
Understanding Atomic Design
Atomic Design is a product design methodology inspired by the atomic structure in chemistry, conceived by Brad Frost. This concept breaks down user interfaces (UI) into smaller, structured components, ranging from the most basic to the most complex. The goal is to create a more consistent, flexible, and manageable design system, especially in large and complex development projects.
Applying Atomic Design principles in Next.js involves organizing your component hierarchy based on the concepts of atoms, molecules, organisms, templates, and pages. This approach helps in creating modular, reusable, and scalable UI components in your Next.js application.
Levels in Atomic Design:
- Atoms: The most basic UI elements, such as buttons, text inputs, headings, colors, and typography. Atoms don’t have meaning on their own but serve as building blocks for more complex components.
- Molecules: Combinations of atoms forming larger components, like simple forms, small navbars, or info cards.
- Organisms: Collections of molecules that form more complex UI sections with specific functions, such as headers, footers, or sidebars.
- Templates: Page structures showing how components are arranged to form different layouts.
- Pages: Concrete implementations of templates with actual content.
For a more detailed understanding of Atomic Design Principles, you can read directly from this link.
Project Setup
Now that you understand the Atomic Design concept introduced by Brad Frost, let’s implement it in our Next.js project. It’s worth noting that this is my implementation and approach; others might have different ways of applying this principle. We’ll be using Next.js 14 with App Router for this demonstration.
For this tutorial, we’ll create simple login and register pages using Atomic Design. The final result will look like this:
Let’s start by initializing our project with the following command:
npx create-next-app atomic-design
For the project setup, I’ve used the following configuration:
√ Would you like to use TypeScript? ... No / Yes
√ Would you like to use ESLint? ... No / Yes
√ Would you like to use Tailwind CSS? ... No / Yes
√ Would you like to use src/ directory? ... No / Yes
√ Would you like to use App Router? (recommended) ... No / Yes
√ Would you like to customize the default import alias (@/*)? ... No / Yes
√ What import alias would you like configured? ... @/*
After initializing the project, open your text editor. Your project structure should look like this:
Let’s make some simple modifications to align our code with the design we’ll implement. First, let’s clean up the globals.css
file to keep only the TailwindCSS setup. The globals.css
file should now look like this:
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Next, since we’ll need icons for the show/hide functionality on password elements, let’s install react-icons with the following command:
npm install react-icons
Implementation
Now that we’ve set up our project, let’s apply the Atomic Design concept to Next.js. First, create a folder named components
in your root directory. This folder will contain our atoms, molecules, organisms, and templates. Inside the components
folder, create subfolders for atoms, molecules, organisms, and templates. Your folder structure should now look like this:
Based on our design, we’ll divide the components as follows:
To make implementing Atomic Design easier, let’s start from the smallest elements and work our way up to larger ones. This approach helps us apply the concept effectively and makes the process more manageable.
1. Atoms
- Definition: Atoms are the smallest building blocks of your UI, representing basic HTML elements.
- Implementation in Next.js: Create individual components for each atomic element, such as
<Button>
,<Input>
,<Heading>
, etc. - Example:
// components/atoms/Input.tsx
import React from "react";
interface InputProps {
type: string;
placeholder: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
className?: string;
}
function Input({
type,
placeholder,
value,
onChange,
className,
}: Readonly<InputProps>) {
return (
<input
type={type}
placeholder={placeholder}
value={value}
onChange={onChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${className}`}
/>
);
}
export default Input;
// components/atoms/Button.tsx
import React from "react";
interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
className?: string;
type?: "button" | "submit" | "reset";
}
function Button({ children, onClick, className, type }: Readonly<ButtonProps>) {
return (
<button
onClick={onClick}
type={type}
className={`px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 ${className}`}
>
{children}
</button>
);
}
export default Button;
2. Molecules
- Definition: Molecules are combinations of atoms that form more complex UI components.
- Implementation in Next.js: Compose molecules by combining multiple atoms together.
- Example:
// components/molecules/PasswordInput.tsx
"use client";
import React, { useState } from "react";
import { BsEye, BsEyeSlash } from "react-icons/bs";
import Input from "@/components/atoms/Input";
interface PasswordInputProps {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
placeholder: string;
}
function PasswordInput({
value,
onChange,
placeholder,
}: Readonly<PasswordInputProps>) {
const [showPassword, setShowPassword] = useState(false);
return (
<div className="relative">
<Input
type={showPassword ? "text" : "password"}
placeholder={placeholder}
value={value}
onChange={onChange}
/>
<button
type="button"
className="absolute right-3 top-1/2 transform -translate-y-1/2"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<BsEyeSlash className="h-5 w-5 text-gray-400" />
) : (
<BsEye className="h-5 w-5 text-gray-400" />
)}
</button>
</div>
);
}
export default PasswordInput;
3. Organisms
- Definition: Organisms are groups of molecules and atoms that form relatively complex UI components and sections.
- Implementation in Next.js: Combine molecules and atoms to create organisms representing distinct sections of your UI.
- Example:
// components/organisms/LoginForm.tsx
"use client";
import React, { useState } from "react";
import Link from "next/link";
import PasswordInput from "@/components/molecules/PasswordInput";
import Input from "@/components/atoms/Input";
import Button from "@/components/atoms/Button";
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Handle login logic here
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<Input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<PasswordInput
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<Button>Login</Button>
<p className="text-center">
Don't have an account?{" "}
<Link href="/register" className="text-blue-500 hover:underline">
Register here
</Link>
</p>
</form>
);
}
export default LoginForm;
// components/organisms/RegisterForm.tsx
"use client";
import React, { useState } from "react";
import Link from "next/link";
import PasswordInput from "@/components/molecules/PasswordInput";
import Input from "@/components/atoms/Input";
import Button from "@/components/atoms/Button";
const RegisterForm = () => {
const [fullName, setFullName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [repeatPassword, setRepeatPassword] = useState("");
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Handle register logic here
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<Input
type="text"
placeholder="Full Name"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
/>
<Input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<PasswordInput
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<PasswordInput
value={repeatPassword}
onChange={(e) => setRepeatPassword(e.target.value)}
placeholder="Repeat Password"
/>
<Button>Register</Button>
<p className="text-center">
Already have an account?{" "}
<Link href="/login" className="text-blue-500 hover:underline">
Login here
</Link>
</p>
</form>
);
};
export default RegisterForm;
4. Templates
- Definition: Templates define the overall layout or structure of your pages using organisms.
- Implementation in Next.js: Create templates by arranging organisms to form page layouts.
- Example:
// components/templates/LoginTemplate.tsx
import React from "react";
import LoginForm from "@/components/organisms/LoginForm";
function LoginTemplate() {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Sign in to your account
</h2>
</div>
<LoginForm />
</div>
</div>
);
}
export default LoginTemplate;
// components/templates/RegisterTemplate.tsx
import React from "react";
import RegisterForm from "@/components/organisms/RegisterForm";
function RegisterTemplate() {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Create your account
</h2>
</div>
<RegisterForm />
</div>
</div>
);
}
export default RegisterTemplate;
5. Pages
- Definition: Pages represent the actual content rendered based on templates.
- Implementation in Next.js: In Next.js app router you can add inside folder app, but if use pages router you can add inside in page. It’s use that utilize templates to structure and render specific content.
- Example:
// app/(auth)/login/page.tsx
import LoginTemplate from "@/components/templates/LoginTemplate";
export default function LoginPage() {
return <LoginTemplate />;
}
// app/(auth)/register/page.tsx
import RegisterTemplate from "@/components/templates/RegisterTemplate";
export default function RegisterPage() {
return <RegisterTemplate />;
}
After following these steps, you should have the complete code, and your folder structure should look like this:
Conclusion
Congratulations! You’ve successfully created a Next.js project implementing the Atomic Design concept. Atomic Design offers a structured methodology for organizing UI components. By applying Atomic Design principles, developers can create more modular, reusable, and maintainable UI architectures in their projects. This approach enhances the scalability and flexibility of the UI development process.
I hope this article has been useful for you and your team, expanding your knowledge even further!
Thank you, and see you in other articles.
If you want to see the complete code, you can visit the following repository: https://github.com/ijlalWindhi/nextjs-atomic-design