Practical Component Design Guide with React × Tailwind CSS × Emotion: The Optimal Approach to Design Systems, State Management, and Reusability
Introduction
Component design is an essential element of modern frontend development. Well-designed components not only improve development efficiency but also greatly contribute to maintainability and scalability.
In this article, we will explain best practices for component design while leveraging a tech stack that includes React, Tailwind CSS, Emotion, Storybook, Figma, and Next.js. Based on the following points, we will introduce guidelines for driving development grounded in a design system.
- Creating component design guidelines (naming conventions based on a design system)
- Component state management (proper use of props, state, and context)
- Abstraction to improve component reusability
- Establishing style guidelines (guidelines to maintain UI consistency)
- Applying theme settings (style management based on themes)
- Incorporating accessibility support (checking accessibility for each component)
- Component version control (introducing proper version management)
- Creating component documentation (detailed usage guides for consumers)
By properly organizing component design, you can not only improve the development speed of your project but also enhance UI consistency and usability. Use the content introduced here as a reference to practice efficient and maintainable component design.
Component Design Guidelines (Naming Conventions Based on a Design System)
We will introduce naming conventions and best practices for designing highly consistent and reusable components based on a design system. By leveraging Tailwind CSS or Emotion, you can enable flexible styling.
Basic Policy for Naming Conventions
Use Atomic Design to classify components at an appropriate level of granularity.
-
Atoms
The smallest unit of UI components, which do not have functionality on their own and are used in combination with other components.- Examples
- Button.tsx
- Input.tsx
- Icon.tsx
- Typography.tsx
- Examples
-
Molecules
Components that combine multiple Atoms to provide a single piece of functionality.- Examples
- SearchBar.tsx (Input + Button)
- UserCard.tsx (Avatar + Text)
- FormField.tsx (Label + Input)
- Examples
-
Organisms
Components that combine Molecules and become major building blocks of a page.- Examples
- Header.tsx (Logo + NavigationMenu + SearchBar)
- Sidebar.tsx (UserProfile + NavigationLinks)
- ProductList.tsx (a collection of ProductCard)
- Examples
Example Implementation of Components Using Tailwind CSS
Button component
// components/atoms/Button.tsx
import { cn } from "@/utils/cn"; // Function to merge Tailwind utilities
type ButtonProps = {
variant?: "primary" | "secondary";
children: React.ReactNode;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
export const Button = ({ variant = "primary", children, className, ...props }: ButtonProps) => {
return (
<button
className={cn(
"px-4 py-2 rounded-md text-white font-medium transition",
variant === "primary" && "bg-blue-500 hover:bg-blue-600",
variant === "secondary" && "bg-gray-500 hover:bg-gray-600",
className
)}
{...props}
>
{children}
</button>
);
};
Key points
- Dynamically combine className with the
cn()function - Switch styles with the
variantproperty - Inherit standard button attributes with
ButtonHTMLAttributes<HTMLButtonElement>
Function to combine Tailwind utilities
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
UserCard component
// components/molecules/UserCard.tsx
import { Button } from "../atoms/Button";
type UserCardProps = {
name: string;
avatarUrl: string;
};
export const UserCard = ({ name, avatarUrl }: UserCardProps) => {
return (
<div className="flex items-center p-4 border border-gray-300 rounded-lg shadow-sm">
<img src={avatarUrl} alt={name} className="w-12 h-12 rounded-full" />
<div className="ml-3">
<h3 className="text-lg font-semibold">{name}</h3>
<Button variant="primary" className="mt-2">Follow</Button>
</div>
</div>
);
};
Key points
- Use
border border-gray-300 rounded-lg shadow-smto set the card border and shadow - Use
w-12 h-12 rounded-fullto make the avatar image circular
Example Implementation of Components Using Emotion
Button component
import { css } from "@emotion/react";
type ButtonProps = {
variant?: "primary" | "secondary";
children: React.ReactNode;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
const buttonBaseStyle = css`
padding: 8px 16px;
border-radius: 4px;
font-size: 16px;
font-weight: 500;
color: white;
transition: background-color 0.2s;
border: none;
cursor: pointer;
`;
const variantStyles = {
primary: css`
background-color: #007bff;
&:hover {
background-color: #0056b3;
}
`,
secondary: css`
background-color: #6c757d;
&:hover {
background-color: #5a6268;
}
`,
};
export const Button = ({ variant = "primary", children, ...props }: ButtonProps) => {
return (
<button css={[buttonBaseStyle, variantStyles[variant]]} {...props}>
{children}
</button>
);
};
Key points
- Use
@emotion/reactand define styles with the css prop - Dynamically change styles according to
variant
Component State Management (Proper Use of props, state, and context)
State management in components is extremely important in React design. By understanding the characteristics of each approach and using them appropriately, you can improve code readability and maintainability.
When to Use props
Use props when passing data from a parent component to a child component.
The basic rule is that “passed data is not modified (immutable).”
Appropriate use cases
- Passing data required for UI display
- Passing parent component state or functions to children
type ButtonProps = {
label: string;
onClick: () => void;
};
const Button = ({ label, onClick }: ButtonProps) => {
return <button onClick={onClick}>{label}</button>;
};
// Parent component
const App = () => {
const handleClick = () => {
console.log("Button was clicked");
};
return <Button label="Click Me" onClick={handleClick} />;
};
Key points
labelis display data → appropriate to pass via propsonClickis an event handler → pass via props and use inside the child component
Cases where you should use props
✅ When passing data from parent to child
✅ When passing data that does not need to be changed
✅ When passing functions to child components
When to Use state
Use state to manage data that changes inside a component.
Appropriate use cases
- Data that changes based on user actions
- Data that only affects the inside of the component
const Modal = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && (
<div>
<p>Modal is open</p>
<button onClick={() => setIsOpen(false)}>Close</button>
</div>
)}
</div>
);
};
Key points
isOpenis a value that changes based on user actions → appropriate to manage with state- Use
setIsOpento update state and change the UI
Cases where you should use state
✅ Data that changes inside a component
✅ Data that changes based on user actions
✅ Temporary data (such as form input values)
When to Use context
Use context to manage data shared across multiple components.
It is used to avoid “prop drilling.”
Appropriate use cases
- Themes or authentication information shared across the entire app
- Data shared across multiple components
const ThemeContext = createContext("light");
const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};
const ThemedComponent = () => {
const { theme, setTheme } = useContext(ThemeContext);
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Switch theme
</button>
</div>
);
};
// Use ThemeProvider for the entire app
const App = () => {
return (
<ThemeProvider>
<ThemedComponent />
</ThemeProvider>
);
};
Key points
- By using
context, you can get and update thethemestate without going throughprops - Placing
ThemeProviderat the top level ofAppapplies theme settings globally
Cases where you should use context
✅ Data to be managed globally (authentication info, theme, etc.)
✅ Data shared across multiple components
✅ When you want to avoid prop drilling
Criteria for Choosing State Management
| State management method | When to use | Concrete use cases |
|---|---|---|
| props | When passing data from parent to child | Button labels, list data, event handlers |
| state | For data that changes inside a component | Form input values, modal open/close state |
| context | For data to be managed globally | Theme, authentication info, language settings |
Abstraction to Improve Component Reusability
To improve component reusability, it is important to design components with appropriate abstraction so they can be reused in different situations. Below are detailed points to enhance reusability.
Design a Generic API
By making the component API (prop design) generic, you can use it in various scenarios.
✅ Concrete example: Button component
By giving it props like variant and size, you can reuse the same component with different styles.
type ButtonProps = {
variant?: "primary" | "secondary" | "outline";
size?: "small" | "medium" | "large";
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
const Button: React.FC<ButtonProps> = ({ variant = "primary", size = "medium", ...props }) => {
const baseStyle = "px-4 py-2 font-semibold rounded";
const variantStyles = {
primary: "bg-blue-500 text-white",
secondary: "bg-gray-500 text-white",
outline: "border border-gray-500 text-gray-500",
};
const sizeStyles = {
small: "text-sm px-2 py-1",
medium: "text-base px-4 py-2",
large: "text-lg px-6 py-3",
};
return (
<button className={`${baseStyle} ${variantStyles[variant]} ${sizeStyles[size]}`} {...props} />
);
};
Key points
- You can get different designs just by specifying
variantandsize - By extending
React.ButtonHTMLAttributes<HTMLButtonElement>, you can also pass standardbuttonprops (such as onClick) - Easy to reuse anywhere
Use the Pattern of Accepting Child Components
By allowing flexible insertion of elements inside a component, it becomes easier to reuse in various situations.
✅ Concrete example: Card component
type CardProps = {
title: string;
children: React.ReactNode;
};
const Card: React.FC<CardProps> = ({ title, children }) => {
return (
<div className="border p-4 shadow rounded-lg">
<h2 className="text-xl font-bold">{title}</h2>
<div className="mt-2">{children}</div>
</div>
);
};
Usage
<Card title="Profile">
<p>Name: John Doe</p>
<p>Age: 30</p>
</Card>
Key points
- By using
children, you can freely customize the content - Only
titleis a fixed part, making it manageable with a simple API
type CardProps = {
header?: React.ReactNode;
footer?: React.ReactNode;
children: React.ReactNode;
};
const Card: React.FC<CardProps> = ({ header, footer, children }) => {
return (
<div className="border p-4 shadow rounded-lg">
{header && <div className="border-b pb-2">{header}</div>}
<div className="mt-2">{children}</div>
{footer && <div className="border-t pt-2">{footer}</div>}
</div>
);
};
Key points
- For more flexible usage, add slots like
headerandfooter
Use Custom Hooks
By extracting component logic into custom hooks, you can reuse the same logic in other components.
✅ Concrete example: useFetch for fetching API data
import { useState, useEffect } from "react";
const useFetch = <T,>(url: string) => {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then((res) => {
if (!res.ok) {
throw new Error("Failed to fetch data");
}
return res.json();
})
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
};
Usage
const UsersList = () => {
const { data: users, loading, error } = useFetch<User[]>(
"https://jsonplaceholder.typicode.com/users"
);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{users?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
Key points
- By using generics in
useFetch<T>(), you can use it with various data types - Manage
loadinganderrorstate and keep component implementation simple - Easy to flexibly respond to API changes
Use the Compound Components Pattern
This is a way to design a more flexible API by grouping multiple components together.
✅ Concrete example: Tabs component
const Tabs = ({ children }: { children: React.ReactNode }) => {
const [activeIndex, setActiveIndex] = useState(0);
return (
<div>
<div className="flex space-x-2 border-b">
{React.Children.map(children, (child, index) =>
React.isValidElement(child) ? (
<button
className={`px-4 py-2 ${index === activeIndex ? "border-b-2 border-blue-500" : ""}`}
onClick={() => setActiveIndex(index)}
>
{child.props.label}
</button>
) : null
)}
</div>
<div className="p-4">{React.Children.toArray(children)[activeIndex]}</div>
</div>
);
};
const Tab = ({ children }: { children: React.ReactNode }) => <>{children}</>;
Usage
<Tabs>
<Tab label="Home">Home content</Tab>
<Tab label="Profile">Profile content</Tab>
<Tab label="Settings">Settings</Tab>
</Tabs>
Key points
- By nesting
<Tab>inside<Tabs>, the API becomes intuitive - By giving
labelas a prop, you can explicitly specify tab titles - Easy to customize the appearance of components
Establishing Style Guidelines (Creating Guidelines to Maintain UI Consistency)
Style guidelines are important documents for maintaining design consistency across the project and preventing misunderstandings among developers. By focusing on the following points, you can enhance UI consistency and improve development efficiency.
Unified Theme
To maintain UI consistency, clearly define design elements such as color palette, fonts, and spacing.
- Color palette
- Define primary, secondary, and accent colors
- Consider color schemes for light mode and dark mode
- Clarify the usage of colors (buttons, backgrounds, text)
Example: Custom theme in Tailwind
module.exports = {
theme: {
extend: {
colors: {
primary: "#1E40AF", // Blue-based main color
secondary: "#9333EA", // Accent color
background: "#F9FAFB", // Background color
text: "#111827", // Text color
},
},
},
};
- Fonts
- Specify base font and heading font
- Standardize font size, line height, and letter spacing
- Consider readability (use Google Fonts or system fonts)
Example: Font settings in Tailwind
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'Helvetica', 'Arial', 'sans-serif'],
heading: ['Poppins', 'sans-serif'],
},
},
},
};
- Spacing
- Standardize spacing between components
- Adopt an 8px grid system (4px increments are also possible)
Example: Spacing settings in Tailwind
module.exports = {
theme: {
extend: {
spacing: {
'4': '16px',
'8': '32px',
'12': '48px',
},
},
},
};
Integration with a Design System
To maintain design consistency and enable smooth collaboration between developers and designers, it is important to use a design system.
- Figma
- Create a component library so designers and developers can use common UI parts
- Clarify styles (colors, fonts, icons, spacing)
- Storybook
- Document components so developers can correctly apply styles
- Instantly confirm UI changes
Unifying Style Management Methods
To perform consistent style management across the team, choose an appropriate method.
Use Tailwind CSS utility classes
- Class-based styling avoids scope conflicts
- Use
@applyto share styles at the component level
Example: Unifying button components with Tailwind
@layer components {
.btn-primary {
@apply px-4 py-2 bg-primary text-white rounded-md hover:bg-opacity-80;
}
}
const Button = ({ children }: { children: React.ReactNode }) => {
return <button className="btn-primary">{children}</button>;
};
Key points
- Use
@applyto integrate utility classes into CSS - Make the
btn-primaryclass reusable in components - Apply unified styles across multiple components
Applying Theme Settings
By centrally managing component styles, you can build a scalable UI while ensuring design consistency.
- Benefits of theme management
- Design consistency
- Applying consistent styles to all components improves UX.
- Improved maintainability
- You can update designs easily by changing styles in one place.
- Easy application of dark mode and brand colors
- By using theme settings, you can smoothly switch between different color schemes.
- Design consistency
Example Implementation (Tailwind CSS + Theme Provider)
By using Tailwind CSS theme settings and next-themes, you can easily apply dark mode and custom themes.
- Setting up ThemeProvider
Usenext-themesto manage theme state and apply it across the page.
import { ThemeProvider } from 'next-themes';
export default function MyApp({ Component, pageProps }) {
return (
<ThemeProvider attribute="class" defaultTheme="light">
<Component {...pageProps} />
</ThemeProvider>
);
}
- Tailwind CSS configuration
Customize Tailwind theme settings so they can be applied per component.
// tailwind.config.js
module.exports = {
darkMode: 'class', // Apply dark mode with class-based approach
theme: {
extend: {
colors: {
primary: '#1e3a8a', // Brand color
secondary: '#9333ea',
},
},
},
};
- Applying themes in components
Use the useTheme hook to allow theme switching.
import { useTheme } from 'next-themes';
export default function ThemeSwitcher() {
const { theme, setTheme } = useTheme();
return (
<button
className="p-2 bg-primary text-white rounded"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}
</button>
);
}
This allows you to easily manage components with extensible dark mode support.
Accessibility-Conscious Design
When designing components, it is important to consider accessibility (a11y).
Why Improve Accessibility
- Makes it easier to use for all users (such as visually impaired users and keyboard-only users)
- Positively affects SEO (search engine optimization)
- Makes it easier to meet legal requirements (such as WCAG compliance)
Basic Accessibility Points
- Use
aria-*attributes appropriately - Consider keyboard operation
- Ensure sufficient contrast ratio
Concrete Accessibility Design Points
- Proper use of
aria-*attributes
By properly settingaria-*attributes, users who use screen readers can more easily understand the content.
Main aria-* attributes
aria-label
Give an appropriate name when buttons or links have no visual label.
<button aria-label="Close">×</button>
aria-labelledby
Reference theidof another element and use it as a label.
<h2 id="section-title">User settings</h2>
<p aria-labelledby="section-title">You can change user settings in this section.</p>
aria-describedby
Specify an element that provides supplementary information.
<input type="text" id="username" aria-describedby="username-desc" />
<p id="username-desc">Please enter username (alphanumeric characters only).</p>
- Proper specification of the
roleattribute
Assign appropriate roles such asrole="dialog"to improve screen reader behavior.
<div role="dialog" aria-labelledby="modal-title">
<h2 id="modal-title">Settings</h2>
<p>You can change account settings here.</p>
</div>
- Consider keyboard operation
Support keyboard operation so that users who cannot use a mouse can still operate the UI.
- Focusable elements
button,a,input,textarea,select, etc. are focusable by default.
SpecifytabIndexfor custom elements.
<div tabIndex={0}>Focusable element</div>
- Add keyboard operation with the
keydownevent
For example, close a modal with theEscapekey.
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
closeModal();
}
};
useEffect(() => {
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, []);
- Ensure contrast ratio
Ensure sufficient contrast for users with color vision deficiencies or low vision.
Recommended contrast ratios
- Normal text: 4.5:1 or higher
- Large text (18px, bold or larger): 3:1 or higher
NG example: Low contrast
button {
background-color: lightgray;
color: white;
}
OK example: Improved contrast ratio
button {
background-color: blue;
color: white;
}
Contrast checking during development
- Chrome DevTools
- WebAIM Contrast Checker
- Form accessibility
Properly associate labels.
- Associate label elements with input
<label htmlFor="email">Email Address</label>
<input type="email" id="email" />
- Properly convey error messages
Usearia-liveto notify error messages in real time.
<p id="error-message" aria-live="polite">
Email AddressPlease enter.
</p>
Role of aria-live
- Used so that screen readers read out dynamically changing content (such as error messages or loading states) at appropriate times.
- For example, applying it to form error messages or updated search results allows you to convey visual changes via audio.
- Modal accessibility
When a modal opens, trap focus inside the modal to support proper operation.
Example: Implementing a focus trap with useEffect
import { useEffect, useRef } from "react";
const Modal = ({ isOpen, onClose }) => {
const modalRef = useRef(null);
useEffect(() => {
if (!isOpen) return;
const focusableElements = modalRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (!focusableElements.length) return;
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
const handleTabKey = (event) => {
if (event.key === "Tab") {
if (event.shiftKey && document.activeElement === firstElement) {
lastElement.focus();
event.preventDefault();
} else if (!event.shiftKey && document.activeElement === lastElement) {
firstElement.focus();
event.preventDefault();
}
}
};
document.addEventListener("keydown", handleTabKey);
return () => document.removeEventListener("keydown", handleTabKey);
}, [isOpen]);
if (!isOpen) return null;
return (
<div
ref={modalRef}
role="dialog"
aria-modal="true"
style={{
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
background: "white",
padding: "20px",
boxShadow: "0px 4px 6px rgba(0,0,0,0.1)",
}}
>
<h2>Modal Title</h2>
<p>Modal content.</p>
<input type="text" placeholder="Input field" />
<button onClick={onClose}>Close</button>
</div>
);
};
export default Modal;
-
What is a focus trap?
A focus trap is a mechanism that keeps focus movement with the Tab key confined inside the modal while it is open. -
Why is a focus trap necessary?
Normally, pressing the Tab key moves focus between focusable elements.
However, when a modal is open, pressing Tab may move focus outside the modal. -
Example
- The modal opens
- You press the Tab key
- Focus moves outside the modal (for example, to a navigation link)
- Even though the modal is open, focus jumps elsewhere, making it hard to operate
-
When you implement a focus trap
- Even if you press the Tab key, you cannot move focus out of the focusable elements inside the modal.
- This allows smooth handling of the modal using only the keyboard.
Component Version Control
By properly managing component versions, you can safely add features and fix bugs while maintaining project stability. Here we will explain concrete management methods and practical approaches.
Importance of Version Control
Proper component version control provides the following benefits:
- Easier to understand the impact range of changes
- Easier to unify understanding among team members
- Easier maintenance of legacy code
- Easier reuse of components across multiple projects
Version control is especially indispensable when developing and providing a component library.
Introducing Semantic Versioning (SemVer)
By introducing Semantic Versioning (SemVer) as the basis for version control, you can clearly classify types of changes.
SemVer rules
MAJOR.MINOR.PATCH
- MAJOR (breaking changes): When you make changes that are not backward compatible with the existing API
- MINOR (feature additions): When you add new features while maintaining compatibility with the existing API
- PATCH (bug fixes): When you fix bugs without changing the behavior of the existing API
{
"version": "2.1.4",
"dependencies": {
"@my-ui/button": "^2.1.0"
}
}
In the above case
2.1.4→ major version 2, minor version 1, patch version 4^2.1.0for@my-ui/button→ automatically updates up to2.1.x
Managing Change History
By recording changes per version in CHANGELOG.md, it becomes easier for developers and users to understand what has changed.
Example of how to write CHANGELOG.md
# Changelog
## [2.1.0] - 2025-03-06
### Added
- Add new `Card` component
- `Button` to component `loading` Add property
## [2.0.0] - 2025-02-20
### Breaking Changes
- `Button` component `size` property changed to `small` / `medium` / `large`
- `Card` component `onClick` Change event arguments
It becomes easier to understand if you create categories such as Added and Breaking Changes.
Linking Versions and Release Tags
By using Git tags, you can clearly manage versions for each release.
How to add a tag
git tag v2.1.0
git push origin v2.1.0
By using tags, you can easily check out code corresponding to a specific version.
git checkout tags/v2.1.0
Version Management Workflow
Below is a concrete workflow to make version management operations smoother.
Example workflow
- New feature addition / fix
- Create a branch such as
feature/new-button-loading - After the fix, merge into main
- Create a branch such as
- Version update
- Update the version in package.json
- Update CHANGELOG.md
- Create a Git tag and push it to main
- Release
- Describe changes in GitHub Releases
- Publish the library with npm publish (if needed)
Creating Component Documentation
It is important to enrich documentation so that consumers (development engineers) can correctly understand and efficiently use components.
Especially as the project grows, having unified documentation prevents misunderstandings among developers and improves reusability.
What to Include in Documentation
Component documentation generally includes the following:
| Section | Description |
|---|---|
| Introduction | Briefly explains the purpose and use cases of the component |
| Usage | Describes basic usage of the component |
| Props and default values | Lists props, their types, and default values |
| Examples | Shows various use cases |
| Styling | Explains how to customize styles with Tailwind, Emotion, etc. |
| Accessibility | Explains WCAG compliance and aria-* attributes |
| Changelog | Describes changes and revision history |
Creating Documentation with Storybook
By using Storybook, you can create documentation while visually checking component usage examples.
Because it can be operated interactively, it is easy for developers and designers to intuitively understand.
First, add Storybook to your project.
npx storybook init
Creating a story file
For example, to create a story for a Button component, prepare Button.stories.tsx as follows:
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
argTypes: {
onClick: { action: 'clicked' },
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Default: Story = {
args: {
label: 'Click me',
},
};
export const Primary: Story = {
args: {
label: 'Click me',
primary: true,
},
};
Using the Docs Addon
By using Storybook’s @storybook/addon-docs, you can write documentation in Markdown or JSX.
npm install @storybook/addon-docs
Add the following to .storybook/main.js
module.exports = {
addons: ['@storybook/addon-docs'],
};
By adding JSDoc comments to components, they can be automatically reflected on the Docs page.
/**
* Basic button component
* @param label - Button label
* @param onClick - clickhandler
*/
export const Button = ({ label, onClick }: { label: string; onClick: () => void }) => {
return <button onClick={onClick}>{label}</button>;
};
Conclusion
In this article, we explained best practices for component design using a tech stack centered on React. By designing based on a design system, you can maintain consistency in development and improve the productivity of the entire team.
Furthermore, by combining elements such as state management, reusability, style unification, accessibility support, and version control, you can build a scalable frontend architecture. In particular, by appropriately combining tools such as visualizing components with Storybook, design collaboration with Figma, and flexible style design with Emotion and Tailwind CSS, you can maximize development efficiency.
Component design is not something that ends once decided; it needs to evolve as the project grows. We hope you will use the content of this article as a reference and adopt it in a way that suits your team’s development style and product.
Questions about this article 📝
If you have any questions or feedback about the content, please feel free to contact us.Go to inquiry form
Related Articles
Chat App (with Image/PDF Sending and Video Call Features)
2024/07/15Building and Operating a Design System Using Chakra UI, ShadCN, and Material UI
2024/03/12Management Dashboard Features (Graph Display, Data Import)
2024/06/02NestJS × React × Railway: Implementing a Blog UI and Deploying to Production
2024/10/25Tutorial for Implementing Authentication with Next.js and Auth.js
2024/09/13Thorough Comparison of the Best ORMs for the Next.js App Router: How to Choose Between Prisma / Drizzle / Kysely / TypeORM [Part 1]
2025/03/13Test Strategy in the Next.js App Router Era: Development Confidence Backed by Jest, RTL, and Playwright
2025/04/22Done in 10 minutes. Easy deployment procedure for a Next.js app using the official AWS Amplify template
2024/11/05
