Maintain consistent file structure - components/ui for base shadcn, components/shared for composed components, pages for routes, always use barrel exports
Scanned 2/12/2026
Install via CLI
openskills install majiayu000/claude-skill-registry---
name: component-organization
description: Maintain consistent file structure - components/ui for base shadcn, components/shared for composed components, pages for routes, always use barrel exports
---
# Component Organization Guide
This guide establishes the file structure and naming conventions for maintaining a consistent, scalable React TypeScript codebase.
## Directory Structure
```
src/
├── assets/ # Static assets (images, icons, fonts)
│ ├── icons/
│ └── images/
├── components/ # Reusable components
│ ├── ui/ # Base shadcn components (CLI-generated)
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── badge.tsx
│ │ └── index.ts # Barrel export
│ ├── shared/ # Composed app-specific components
│ │ ├── UserProfileCard.tsx
│ │ ├── DataTable.tsx
│ │ └── index.ts # Barrel export
│ └── index.ts # Root barrel export
├── pages/ # Top-level route components
│ ├── Home.tsx
│ ├── About.tsx
│ ├── NotFound.tsx
│ └── index.ts # Barrel export
├── layouts/ # Layout wrappers
│ ├── MainLayout.tsx
│ ├── DashboardLayout.tsx
│ └── index.ts # Barrel export
├── features/ # Feature-based organization (optional)
│ ├── authentication/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── index.ts
│ └── dashboard/
│ ├── components/
│ ├── hooks/
│ └── index.ts
├── hooks/ # Custom React hooks
│ ├── useLocalStorage.ts
│ └── index.ts # Barrel export
├── lib/ # Utilities and helpers
│ └── utils.ts # cn() helper for Tailwind
├── services/ # API and external services
│ ├── api.service.ts
│ └── index.ts # Barrel export
├── constants/ # App-wide constants
│ └── index.ts # APP_NAME, ROUTES, etc.
├── types/ # TypeScript type definitions
│ ├── api.types.ts
│ ├── common.types.ts
│ └── index.ts # Barrel export
└── utils/ # Pure utility functions
└── index.ts
```
## Directory Purposes
### `components/ui/`
**Purpose**: Base shadcn/ui components generated via CLI
- **DO**: Add new components using `npx shadcn@latest add [component]`
- **DO**: Keep these components pure and unopinionated
- **DON'T**: Modify these files directly (regenerate if needed)
- **DON'T**: Add business logic here
**Example**:
```tsx
// components/ui/button.tsx (generated by shadcn CLI)
import * as React from "react"
import { cn } from "@/lib/utils"
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
```
### `components/shared/`
**Purpose**: Composed, app-specific components built from base UI components
- **DO**: Create components that combine multiple `ui/` components
- **DO**: Add business logic and app-specific styling
- **DO**: Make these components reusable across the app
- **DON'T**: Use for one-off components (put those in features or pages)
**Example**:
```tsx
// components/shared/UserProfileCard.tsx
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
export interface UserProfileCardProps {
name: string;
role: string;
avatar: string;
}
export function UserProfileCard({ name, role, avatar }: UserProfileCardProps) {
return (
<Card>
<CardHeader>
<img src={avatar} alt={name} className="h-16 w-16 rounded-full" />
<CardTitle>{name}</CardTitle>
<Badge>{role}</Badge>
</CardHeader>
<CardContent>
{/* Additional user details */}
</CardContent>
</Card>
);
}
```
### `pages/`
**Purpose**: Top-level route components that render at specific URLs
- **DO**: Keep components focused on layout and orchestration
- **DO**: Use descriptive names matching routes (Home.tsx, About.tsx)
- **DON'T**: Put heavy business logic here (extract to hooks/services)
**Example**:
```tsx
// pages/Home.tsx
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { APP_NAME } from '@/constants';
export function Home() {
const features = [
{
title: 'Vite',
badge: 'Fast',
description: 'Lightning-fast build tool with HMR.',
gradient: 'from-violet-500 to-purple-500',
},
// ... more features
];
return (
<div className="space-y-32">
{/* Hero Section */}
<div className="relative overflow-hidden">
<h1 className="text-6xl font-extrabold">{APP_NAME}</h1>
<p className="text-xl text-gray-600">
A modern React TypeScript starter
</p>
</div>
{/* Features Grid */}
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{features.map((feature) => (
<Card key={feature.title}>
<CardHeader>
<CardTitle>{feature.title}</CardTitle>
<Badge>{feature.badge}</Badge>
<CardDescription>{feature.description}</CardDescription>
</CardHeader>
</Card>
))}
</div>
</div>
);
}
```
### `layouts/`
**Purpose**: Wrapper components that provide consistent structure across pages
- **DO**: Handle navigation, headers, footers, sidebars
- **DO**: Use `<Outlet />` from react-router for nested routing
- **DON'T**: Include page-specific content
**Example**:
```tsx
// layouts/MainLayout.tsx
import { Link, Outlet, useLocation } from 'react-router-dom';
export function MainLayout() {
const location = useLocation();
const isActive = (path: string) => location.pathname === path;
return (
<div className="min-h-screen bg-white">
{/* Navigation Header */}
<header className="sticky top-0 z-50 border-b bg-white/95">
<nav className="mx-auto max-w-7xl px-8 py-4">
<div className="flex items-center gap-8">
<Link
to="/"
className={`px-4 py-2 font-medium ${
isActive('/') ? 'text-blue-600' : 'text-gray-600'
}`}
>
Home
</Link>
<Link
to="/about"
className={`px-4 py-2 font-medium ${
isActive('/about') ? 'text-blue-600' : 'text-gray-600'
}`}
>
About
</Link>
</div>
</nav>
</header>
{/* Main Content */}
<main className="mx-auto max-w-7xl px-8 py-16">
<Outlet />
</main>
</div>
);
}
```
### `lib/`
**Purpose**: Core utilities and helper functions
- **DO**: Keep the `cn()` helper for Tailwind class merging
- **DO**: Add utilities that integrate external libraries
- **DON'T**: Put pure business logic here (use `utils/` instead)
**Example**:
```tsx
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
### `constants/`
**Purpose**: Application-wide constants and configuration
- **DO**: Use SCREAMING_SNAKE_CASE for constant names
- **DO**: Use `as const` for type safety
- **DON'T**: Put environment variables here (use import.meta.env)
**Example**:
```tsx
// constants/index.ts
export const APP_NAME = 'Vite React TypeScript Boilerplate';
export const API_BASE_URL =
import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api';
export const ROUTES = {
HOME: '/',
ABOUT: '/about',
NOT_FOUND: '*',
} as const;
export const STORAGE_KEYS = {
AUTH_TOKEN: 'auth_token',
USER_PREFERENCES: 'user_preferences',
} as const;
```
## Barrel Exports
**Always create `index.ts` files** to enable clean imports and maintain a clear public API.
### Pattern 1: Named Exports (Recommended)
```ts
// components/ui/index.ts
export { Button } from './button';
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
} from './card';
export { Badge } from './badge';
export { Separator } from './separator';
```
### Pattern 2: Re-export All
```ts
// Only use when all exports from a module should be public
export * from './button';
export * from './card';
```
### Pattern 3: Nested Barrel Exports
```ts
// components/index.ts - Exposes nested directories
export { Button } from './ui/button';
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
} from './ui/card';
```
**Benefits**:
- Clean imports: `import { Button, Card } from '@/components'`
- Refactoring safety: Move files without breaking imports
- Clear public API: Only exported items are "public"
## Naming Conventions
### Components
**Format**: PascalCase
- Files: `Button.tsx`, `UserProfileCard.tsx`, `MainLayout.tsx`
- Components: `export function Button()`, `export function UserProfileCard()`
### Utilities and Services
**Format**: camelCase or kebab-case
- Files: `utils.ts`, `api.service.ts`, `useLocalStorage.ts`
- Functions: `export function cn()`, `export function useLocalStorage()`
### Directories
**Format**: kebab-case
- `components/ui`, `components/shared`, `pages`, `layouts`
### Constants
**Format**: SCREAMING_SNAKE_CASE
- `APP_NAME`, `API_BASE_URL`, `STORAGE_KEYS`
### Types and Interfaces
**Format**: PascalCase with descriptive suffix
- `UserProfileCardProps`, `ApiResponse`, `AuthState`
## Decision Flowchart: Where to Place New Components
```
┌─────────────────────────────────────┐
│ Need to create a new component? │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Is it a shadcn/ui base component? │
└──────────────┬──────────────────────┘
│
┌─────┴─────┐
│ │
YES NO
│ │
▼ ▼
┌─────────┐ ┌──────────────────────────────────┐
│ Use CLI │ │ Is it used in 2+ different │
│ to add │ │ features/pages? │
│ to ui/ │ └──────────────┬───────────────────┘
└─────────┘ │
┌─────┴─────┐
│ │
YES NO
│ │
▼ ▼
┌──────────────────┐ ┌─────────────────────┐
│ Does it compose │ │ Is it a full page │
│ multiple ui/ │ │ route? │
│ components? │ └──────────┬──────────┘
└────────┬─────────┘ │
│ ┌────────┴────────┐
┌─────┴─────┐ │ │
│ │ YES NO
YES NO │ │
│ │ ▼ ▼
▼ ▼ ┌────────┐ ┌──────────────┐
┌──────────┐ ┌────────┐│ pages/ │ │ Is it part │
│ shared/ │ │ hooks/ ││ │ │ of a cohesive│
│ │ │ or │└────────┘ │ feature? │
└──────────┘ │ utils/ │ └──────┬───────┘
└────────┘ │
┌────────┴────────┐
│ │
YES NO
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ features/ │ │ Colocate with│
│ [feature]/ │ │ parent │
│ components/ │ │ component │
└──────────────┘ └──────────────┘
```
## When to Create New Components
### DRY Principle: Reusability > 2x Usage
If you're copying the same JSX structure more than twice, extract it into a component.
**Before**:
```tsx
// Multiple pages with duplicated card structure
export function Dashboard() {
return (
<Card>
<CardHeader>
<CardTitle>Stats</CardTitle>
</CardHeader>
<CardContent>{/* ... */}</CardContent>
</Card>
);
}
export function Profile() {
return (
<Card>
<CardHeader>
<CardTitle>User Info</CardTitle>
</CardHeader>
<CardContent>{/* ... */}</CardContent>
</Card>
);
}
```
**After**:
```tsx
// components/shared/StatsCard.tsx
export function StatsCard({ title, children }) {
return (
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>{children}</CardContent>
</Card>
);
}
```
### Complexity: Component Logic > 50 Lines
If a component or section exceeds 50 lines, consider extracting it.
**Before**:
```tsx
export function Dashboard() {
return (
<div>
{/* 80 lines of user profile UI */}
<div className="user-profile">
{/* Complex profile rendering */}
</div>
{/* 60 lines of statistics UI */}
<div className="statistics">
{/* Complex stats rendering */}
</div>
</div>
);
}
```
**After**:
```tsx
export function Dashboard() {
return (
<div>
<UserProfile />
<Statistics />
</div>
);
}
```
### Composition: Building from Multiple Base Components
When combining 3+ base UI components, create a composed component.
```tsx
// components/shared/UserCard.tsx - Composes Card, Badge, Button
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
export function UserCard({ user }) {
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>{user.name}</CardTitle>
<Badge>{user.role}</Badge>
</div>
</CardHeader>
<CardContent>
<p>{user.email}</p>
<Button variant="outline">View Profile</Button>
</CardContent>
</Card>
);
}
```
### Domain Logic: App-Specific Behavior
When adding business logic, API calls, or state management, extract to a component.
```tsx
// components/shared/ProductList.tsx - Contains domain logic
import { useEffect, useState } from 'react';
import { fetchProducts } from '@/services/api.service';
import { Card } from '@/components/ui/card';
export function ProductList() {
const [products, setProducts] = useState([]);
useEffect(() => {
fetchProducts().then(setProducts);
}, []);
return (
<div className="grid gap-4">
{products.map(product => (
<Card key={product.id}>
{/* Product display */}
</Card>
))}
</div>
);
}
```
## Real-World Examples from This Project
### Example 1: UI Component Barrel Export
```ts
// src/components/ui/index.ts
export { Button } from './button';
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
} from './card';
export { Badge } from './badge';
export { Separator } from './separator';
```
### Example 2: Page Component with Feature Imports
```tsx
// src/pages/Home.tsx
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { APP_NAME } from '@/constants';
export function Home() {
// Page orchestrates UI components
return (
<div className="space-y-32">
<h1>{APP_NAME}</h1>
{/* Feature showcase using composed components */}
</div>
);
}
```
### Example 3: Layout with Router Integration
```tsx
// src/layouts/MainLayout.tsx
import { Link, Outlet, useLocation } from 'react-router-dom';
export function MainLayout() {
const location = useLocation();
return (
<div className="min-h-screen">
<header className="sticky top-0">
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
</header>
<main>
<Outlet /> {/* Pages render here */}
</main>
</div>
);
}
```
### Example 4: Constants with Type Safety
```ts
// src/constants/index.ts
export const APP_NAME = 'Vite React TypeScript Boilerplate';
export const ROUTES = {
HOME: '/',
ABOUT: '/about',
NOT_FOUND: '*',
} as const;
// Usage in pages
import { ROUTES } from '@/constants';
<Link to={ROUTES.HOME}>Home</Link>
```
## Quick Reference Checklist
- [ ] Created barrel export `index.ts` for new directory?
- [ ] Used PascalCase for component files and exports?
- [ ] Used kebab-case for directory names?
- [ ] Added shadcn components via CLI to `components/ui/`?
- [ ] Placed composed components in `components/shared/`?
- [ ] Put route components in `pages/` directory?
- [ ] Exported layouts from `layouts/` directory?
- [ ] Used SCREAMING_SNAKE_CASE for constants?
- [ ] Added TypeScript types for component props?
- [ ] Verified component is reused 2+ times before creating?
- [ ] Extracted complex logic (>50 lines) to separate component?
- [ ] Used `@/` path alias for imports?
## Anti-Patterns to Avoid
**DON'T modify shadcn UI components directly**:
```tsx
// ❌ Bad: Editing ui/button.tsx manually
// If you need customization, create a wrapper in shared/
```
**DON'T skip barrel exports**:
```tsx
// ❌ Bad: Direct imports without barrel
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
// ✅ Good: Use barrel exports
import { Button, Card } from '@/components/ui';
```
**DON'T create one-off components in shared/**:
```tsx
// ❌ Bad: Single-use component in shared/
// components/shared/HomepageHeroSection.tsx (only used once)
// ✅ Good: Keep in the page or feature
// pages/Home.tsx (inline) or features/homepage/components/
```
**DON'T mix concerns in constants**:
```tsx
// ❌ Bad: Mixing types and runtime values
export const ENDPOINTS = {
users: '/api/users',
auth: '/api/auth',
};
export type User = { id: string; name: string };
// ✅ Good: Separate constants and types
// constants/index.ts - runtime values
// types/index.ts - type definitions
```
---
**Remember**: Good organization makes scaling easy. When in doubt, follow the principle: "Start simple, extract when you repeat."
No comments yet. Be the first to comment!