Maintain consistent file structure - components/ui for base shadcn, components/shared for composed components, pages for routes, always use barrel exports
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
npx agent-skills-cli listSkill Instructions
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:
// 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:
// 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:
// 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:
// 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:
// 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 constfor type safety - 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 index.ts files to enable clean imports and maintain a clear public API.
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.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:
// 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
index.tsfor 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:
// β 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."
More by JewelsHovan
View allAggregate and analyze AI news from 7 authoritative sources including expert newsletters (Andrew Ng's The Batch), research papers (HuggingFace), industry news (TechCrunch, AI News), and community discussions (Reddit, Hacker News). Provides deep trend analysis with expert sentiment and community opinions. This skill should be used when the user wants a comprehensive AI news digest, research recent developments, understand community sentiment, or stay updated on AI trends. Invoke with `/ai-news <days>` (e.g., `/ai-news 3` for past 3 days).
Standardize page structure - always use MainLayout wrapper, space-y-32 for sections, px-8 py-16 for page padding, responsive grid patterns
Guide for proper shadcn-ui component usage - use Card for wrapping/layout, compose from base components, never modify components/ui directly
Tailwind CSS v4 styling guidelines - use @theme blocks for configuration, hsl(var(--color-x)) for colors, never hardcode values, use responsive utilities
