Claude-skill-registry component-organization
Maintain consistent file structure - components/ui for base shadcn, components/shared for composed components, pages for routes, always use barrel exports
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/component-organization" ~/.claude/skills/majiayu000-claude-skill-registry-component-organization && rm -rf "$T"
skills/data/component-organization/SKILL.mdComponent 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/
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:
// 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/
components/shared/Purpose: Composed, app-specific components built from base UI components
- DO: Create components that combine multiple
componentsui/ - 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:
// 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/
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:
// 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/
layouts/Purpose: Wrapper components that provide consistent structure across pages
- DO: Handle navigation, headers, footers, sidebars
- DO: Use
from react-router for nested routing<Outlet /> - DON'T: Include page-specific content
Example:
// 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/
lib/Purpose: Core utilities and helper functions
- DO: Keep the
helper for Tailwind class mergingcn() - DO: Add utilities that integrate external libraries
- DON'T: Put pure business logic here (use
instead)utils/
Example:
// lib/utils.ts import { clsx, type ClassValue } from 'clsx'; import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
constants/
constants/Purpose: Application-wide constants and configuration
- DO: Use SCREAMING_SNAKE_CASE for constant names
- DO: Use
for type safetyas const - DON'T: Put environment variables here (use import.meta.env)
Example:
// 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
files to enable clean imports and maintain a clear public API.index.ts
Pattern 1: Named Exports (Recommended)
// 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
// Only use when all exports from a module should be public export * from './button'; export * from './card';
Pattern 3: Nested Barrel Exports
// 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.tsxMainLayout.tsx - Components:
,export function Button()export function UserProfileCard()
Utilities and Services
Format: camelCase or kebab-case
- Files:
,utils.ts
,api.service.tsuseLocalStorage.ts - Functions:
,export function cn()export function useLocalStorage()
Directories
Format: kebab-case
,components/ui
,components/shared
,pageslayouts
Constants
Format: SCREAMING_SNAKE_CASE
,APP_NAME
,API_BASE_URLSTORAGE_KEYS
Types and Interfaces
Format: PascalCase with descriptive suffix
,UserProfileCardProps
,ApiResponseAuthState
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:
// 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:
// 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:
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:
export function Dashboard() { return ( <div> <UserProfile /> <Statistics /> </div> ); }
Composition: Building from Multiple Base Components
When combining 3+ base UI components, create a composed component.
// 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.
// 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
// 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
// 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
// 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
// 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
for new directory?index.ts - 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
directory?pages/ - Exported layouts from
directory?layouts/ - 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:
// ❌ Bad: Editing ui/button.tsx manually // If you need customization, create a wrapper in shared/
DON'T skip barrel exports:
// ❌ 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/:
// ❌ 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:
// ❌ 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."