---
name: component-architecture
description: Reusable component patterns for cards, sections, forms, and layouts with consistent prop interfaces and composition strategies. Use when creating new components, refactoring existing ones, or establishing component design patterns.
---
# Component Architecture
## Folder Structure
```
components/
├── ui/ # shadcn/ui base components
│ ├── button.tsx
│ ├── card.tsx
│ └── ...
├── layout/ # Page structure components
│ ├── Header.tsx
│ ├── Footer.tsx
│ ├── MobileNav.tsx
│ └── LanguageSwitcher.tsx
├── sections/ # Home page sections
│ ├── Hero.tsx
│ ├── TrustRow.tsx
│ ├── ServicesPreview.tsx
│ ├── AboutPreview.tsx
│ └── LeadCapture.tsx
├── cards/ # Reusable card components
│ ├── ServiceCard.tsx
│ ├── ProgrammeCard.tsx
│ ├── TestimonialCard.tsx
│ └── PricingCard.tsx
├── forms/ # Form components
│ ├── ContactForm.tsx
│ ├── BookingForm.tsx
│ └── LeadCaptureForm.tsx
├── shared/ # Utility components
│ ├── Container.tsx
│ ├── SectionHeader.tsx
│ ├── CTAButton.tsx
│ └── Breadcrumbs.tsx
└── seo/ # SEO components
├── JsonLd.tsx
└── OrganizationJsonLd.tsx
```
## Component Template
```tsx
// components/cards/ServiceCard.tsx
import Link from 'next/link';
import { ArrowRight } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import type { Service } from '@/types';
interface ServiceCardProps {
service: Service;
locale: string;
}
export function ServiceCard({ service, locale }: ServiceCardProps) {
return (
{service.name}
{service.duration}
{service.shortDesc}
{service.tags.map((tag) => (
{tag}
))}
From €{service.priceFrom}
Learn more
);
}
```
## Section Component Pattern
```tsx
// components/sections/ServicesPreview.tsx
import { getTranslations } from 'next-intl/server';
import { getServices } from '@/lib/data';
import { Container } from '@/components/shared/Container';
import { SectionHeader } from '@/components/shared/SectionHeader';
import { ServiceCard } from '@/components/cards/ServiceCard';
import { CTAButton } from '@/components/shared/CTAButton';
import type { Locale } from '@/i18n.config';
interface ServicesPreviewProps {
locale: Locale;
}
export async function ServicesPreview({ locale }: ServicesPreviewProps) {
const t = await getTranslations({ locale, namespace: 'home.services' });
const services = await getServices(locale);
const featured = services.slice(0, 4);
return (
{featured.map((service) => (
))}
{t('viewAll')}
);
}
```
## Shared Components
```tsx
// components/shared/Container.tsx
import { cn } from '@/lib/utils';
interface ContainerProps {
children: React.ReactNode;
className?: string;
size?: 'default' | 'narrow' | 'wide';
}
export function Container({
children,
className,
size = 'default',
}: ContainerProps) {
return (
{children}
);
}
```
```tsx
// components/shared/SectionHeader.tsx
import { cn } from '@/lib/utils';
interface SectionHeaderProps {
title: string;
subtitle?: string;
centered?: boolean;
className?: string;
}
export function SectionHeader({
title,
subtitle,
centered = false,
className,
}: SectionHeaderProps) {
return (
{title}
{subtitle && (
{subtitle}
)}
);
}
```
```tsx
// components/shared/CTAButton.tsx
import Link from 'next/link';
import { Button, type ButtonProps } from '@/components/ui/button';
import { ArrowRight } from 'lucide-react';
interface CTAButtonProps extends ButtonProps {
href: string;
children: React.ReactNode;
showArrow?: boolean;
}
export function CTAButton({
href,
children,
showArrow = false,
...props
}: CTAButtonProps) {
return (
);
}
```
## Composition Pattern
```tsx
// Compound components for flexibility
// components/cards/PricingCard.tsx
import { Card, CardContent, CardHeader } from '@/components/ui/card';
interface PricingCardProps {
children: React.ReactNode;
featured?: boolean;
}
export function PricingCard({ children, featured }: PricingCardProps) {
return (
{children}
);
}
PricingCard.Header = function PricingCardHeader({
title,
price,
period,
}: {
title: string;
price: number;
period?: string;
}) {
return (
{title}
€{price}
{period && /{period}}
);
};
PricingCard.Features = function PricingCardFeatures({
features,
}: {
features: string[];
}) {
return (
{features.map((feature, i) => (
-
{feature}
))}
);
};
// Usage
```
## Props Interface Conventions
```tsx
// Consistent prop naming
interface ComponentProps {
// Content
title: string;
subtitle?: string;
description?: string;
// Data
items: Item[];
data?: DataType;
// Variants
variant?: 'default' | 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
// State
isLoading?: boolean;
disabled?: boolean;
// Styling
className?: string;
// Events
onClick?: () => void;
onSubmit?: (data: FormData) => void;
// i18n
locale: string;
// Children
children?: React.ReactNode;
}
```
## Empty States
```tsx
// components/shared/EmptyState.tsx
interface EmptyStateProps {
title: string;
description?: string;
action?: React.ReactNode;
}
export function EmptyState({ title, description, action }: EmptyStateProps) {
return (
{title}
{description && (
{description}
)}
{action &&
{action}
}
);
}
```