Design systems are the backbone of consistent user interfaces. Here's how to build one that scales—along with a solid project structure to support it.
Why Design Systems Matter
A well-crafted design system provides:
- Consistency across all products
- Faster development with reusable components
- Better collaboration between designers and developers
- Reduced technical debt over time
Core Principles
1. Start with Tokens
Design tokens are the atomic values of your system:
export const tokens = { colors: { primary: { 50: '#eff6ff', 500: '#3b82f6', 900: '#1e3a8a', }, neutral: { 0: '#ffffff', 100: '#f5f5f5', 900: '#171717', }, }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '2rem', }, radii: { sm: '0.25rem', md: '0.5rem', lg: '1rem', full: '9999px', }, } as const;
2. Build Primitive Components
Start with the basics:
import { cva, type VariantProps } from "class-variance-authority"; const buttonVariants = cva( "inline-flex items-center justify-center rounded-md font-medium transition-colors", { variants: { variant: { primary: "bg-primary text-white hover:bg-primary/90", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", }, size: { sm: "h-8 px-3 text-sm", md: "h-10 px-4", lg: "h-12 px-6 text-lg", }, }, defaultVariants: { variant: "primary", size: "md", }, } ); interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {} export function Button({ variant, size, className, ...props }: ButtonProps) { return ( <button className={buttonVariants({ variant, size, className })} {...props} /> ); }
Component Composition
Build complex components from primitives:
| Level | Examples | Purpose |
|---|---|---|
| Tokens | Colors, spacing, typography | Foundation |
| Primitives | Button, Input, Badge | Building blocks |
| Patterns | Card, Modal, Dropdown | Common UI patterns |
| Templates | PageHeader, Sidebar | Layout structures |
Project Structure & Setup
Framework
- Nextjs
Libraries
- ShadCn
- Prettier
- Eslint
- Zod
- Dnd-kit
- Tailwind-css
- React-markdown
- Copilotkit
- Driver.js
- ReverseUI (animations)
- Framer motion
- React360 (vr website)
- Swiper
Imports structure
- React stuff
- composants
- Hooks & messages and css
Function structure
- State variables
- Hooks
- Callbacks
- Variables
- Functions
- UseEffects
Folder structure
Section like admin/user/super admin/dashboard
Components
- Components/atoms
- Components/molecules (uses atoms)
- Components/section (uses molecules)
-
Components/<comp-name>(uses sections) - Components/ui (optional, external stuff like shadcn)
Pages
- ?
Pages/section/section-name? -
Pages/<PageName>/<page-name>
Layout
-
Layout/section/<section-name>
Hooks
-
Hooks/section/<hook-name>
Context
-
Context/section/<context-name>
Utils
-
Utils/<util-name>
Libs
-
Libs/<lib-name>
Boiler plate
- 404 not found page
- Landing page
- About us page
- Contact page with form
- Dashboard with draggable widgets and charts
Documentation is Key
"A design system without documentation is just a component library."
Every component should include:
- Usage examples - Show common use cases
- Props documentation - Explain all options
- Accessibility notes - ARIA labels, keyboard nav
- Do's and Don'ts - Guide proper usage
Versioning Strategy
{ "name": "@company/design-system", "version": "2.1.0", "peerDependencies": { "react": "^18.0.0", "tailwindcss": "^3.0.0" } }
Use semantic versioning:
- Major: Breaking changes
- Minor: New features (backward compatible)
- Patch: Bug fixes
Conclusion
Building a design system is an investment that pays dividends in:
- Developer productivity
- Design consistency
- User experience
- Team collaboration
Start small, iterate often, and document everything.