Component composition patterns
Skill ceilidhboy/skills/skills/component-composition-patterns
Agent skills for Laravel and PHP development. Installable via npx skills@latest add ceilidhboy/skills
npx -y skills add ceilidhboy/skills --skill component-composition-patternsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Building reusable components through composition using generic base components with specific wrapper components that provide styling, configuration, and domain-specific behavior.
SKILL.md
4.4 KB, as published. Nobody here has run it
Component Composition Patterns
When to Activate This Skill
Activate this skill whenever you are:
- Creating reusable components that will be used in multiple contexts with different styling
- Building a generic base component and specific wrapper components around it
- Implementing configuration-driven component behavior
- Separating presentation logic from styling/theming logic
- Creating components that follow the composition pattern (generic + wrappers)
The Composition Pattern
This pattern separates concerns into three layers:
- Generic Base Component — Pure presentation, no styling decisions
- Wrapper Components — Domain-specific styling and configuration
- Configuration Files — Type definitions and styling mappings
Example: Statistics Widget Pattern
Layer 1: Generic Base Component (shared/Statistics.tsx)
// Pure presentation - accepts all styling as props
export function Statistics({
label,
icon: Icon,
value,
amountInCents,
colorPalette
}: StatisticsProps) {
return (
<div className={cn('relative flex flex-col gap-4 px-4 py-4 rounded-lg border',
colorPalette.bgColor,
colorPalette.borderColor)}>
{/* Icon */}
<div className="absolute top-3 right-3">
<div className={cn('flex items-center justify-center size-10 rounded-md',
colorPalette.iconBgColor)}>
<Icon className={cn('size-6', colorPalette.textColor)} />
</div>
</div>
{/* Label and Value */}
<span className="text-sm text-slate-500 pr-12">{label}</span>
<div className="flex items-baseline justify-between">
<span className="text-xl font-bold">{value}</span>
{amountInCents && <span className={cn('text-sm font-medium', colorPalette.textColor)}>
{formatCurrency(amountInCents)}
</span>}
</div>
</div>
);
}
Layer 2: Wrapper Component (action-required/ActionStatistics.tsx)
// Domain-specific wrapper - handles ActionRequired styling
export function ActionStatistics({ type, value, amountInCents }: ActionStatisticsProps) {
const config = generateWidgetConfig(type);
return (
<Statistics
label={config.label}
icon={config.icon}
value={value}
amountInCents={amountInCents}
colorPalette={config.colorPalette}
/>
);
}
Layer 3: Configuration (action-required/config.ts)
// Type definitions and styling mappings
export type ActionWidgetType = 'jobsNeedingWorkers' | 'overdueInvoices' | ...;
const widgetTypeToColorPalette: Record<ActionWidgetType, ColorPalette> = {
jobsNeedingWorkers: 'amber',
overdueInvoices: 'red',
// ...
};
export function generateWidgetConfig(type: ActionWidgetType): Config {
const palette = widgetTypeToColorPalette[type];
return {
label: labelMap[type],
icon: iconMap[type],
colorPalette: colorSchemes[palette],
};
}
Benefits
- DRY: Widget layout logic lives in one place
- Flexible: Each domain (ActionRequired, KeyPerformance) has its own styling
- Maintainable: Changes to widget structure only need to be made once
- Type Safe: Each domain has its own type definitions
- Scalable: Easy to add new domains without modifying the base component
File Structure
resources/js/components/dashboard/
├── shared/
│ ├── Statistics.tsx # Generic base component
│ └── index.ts
├── action-required/
│ ├── ActionStatistics.tsx # Wrapper for ActionRequired
│ ├── config.ts # ActionRequired config
│ └── index.ts
└── key-performance/
├── KeyPerformanceStatistics.tsx # Wrapper for KeyPerformance
├── config.ts # KeyPerformance config
└── index.ts
Key Principles
- Separation of Concerns: Base component handles layout, wrappers handle styling
- Configuration-Driven: All styling decisions live in config files
- Type Safety: Each domain has its own type definitions
- Parallel Structure: Mirror the structure across different domains for consistency
- Minimal Props: Base component accepts only what it needs to render