specwright/templates/skills/dev-team/frontend/ui-component-architecture/SKILL.md
# UI Component Architecture > Skill Template: Frontend Development > Category: Component Design & Composition > Version: 1.0.0 > Created: 2026-01-09 ## Quick Reference <!-- This section is extracted by Orchestrator for task prompts (~50-100 lines) --> **When to use:** UI Components, Props Design, Component Composition, Layout **Key Patterns:** 1. **Component Structure** - One component per file - Props interface at top - Hooks grouped together - Event handlers as separate funct
npx skillsauth add michsindlinger/specwright specwright/templates/skills/dev-team/frontend/ui-component-architectureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Skill Template: Frontend Development Category: Component Design & Composition Version: 1.0.0 Created: 2026-01-09
When to use: UI Components, Props Design, Component Composition, Layout
Key Patterns:
Component Structure
Composition over Inheritance
Props Design
Performance Rules
Quick Example (React):
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
children: React.ReactNode;
onClick?: () => void;
}
export function Button({
variant = 'primary',
size = 'md',
isLoading = false,
children,
onClick,
...props
}: ButtonProps) {
return (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={isLoading}
onClick={onClick}
{...props}
>
{isLoading ? <Spinner /> : children}
</button>
);
}
Anti-Patterns to Avoid:
Design and implement scalable, reusable UI component architectures using modern composition patterns, component hierarchies, and design system principles.
Activate this skill when:
// Compound Component Pattern
const Card = ({ children, className }) => {
return <div className={`card ${className}`}>{children}</div>;
};
Card.Header = ({ children, className }) => {
return <div className={`card-header ${className}`}>{children}</div>;
};
Card.Body = ({ children, className }) => {
return <div className={`card-body ${className}`}>{children}</div>;
};
Card.Footer = ({ children, className }) => {
return <div className={`card-footer ${className}`}>{children}</div>;
};
// Usage
<Card>
<Card.Header>Title</Card.Header>
<Card.Body>Content</Card.Body>
<Card.Footer>Actions</Card.Footer>
</Card>
// Composition with Custom Hooks
const useToggle = (initialValue = false) => {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue(v => !v), []);
return [value, toggle];
};
const Accordion = ({ title, children }) => {
const [isOpen, toggle] = useToggle(false);
return (
<div className="accordion">
<button onClick={toggle} className="accordion-trigger">
{title}
</button>
{isOpen && <div className="accordion-content">{children}</div>}
</div>
);
};
// Render Props Pattern
const DataFetcher = ({ url, render }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return render({ data, loading, error });
};
// Usage
<DataFetcher
url="/api/users"
render={({ data, loading, error }) => (
loading ? <Spinner /> : <UserList users={data} />
)}
/>
// Controlled Component Pattern
const Input = ({ value, onChange, label, error, ...props }) => {
return (
<div className="input-wrapper">
{label && <label className="input-label">{label}</label>}
<input
value={value}
onChange={(e) => onChange(e.target.value)}
className={`input ${error ? 'input-error' : ''}`}
{...props}
/>
{error && <span className="input-error-message">{error}</span>}
</div>
);
};
// Polymorphic Component Pattern
const Button = ({ as: Component = 'button', children, variant = 'primary', ...props }) => {
return (
<Component
className={`btn btn-${variant}`}
{...props}
>
{children}
</Component>
);
};
// Usage
<Button>Click me</Button>
<Button as="a" href="/link">Link Button</Button>
<!-- Compound Component Pattern with Slots -->
<template>
<div class="card">
<div v-if="$slots.header" class="card-header">
<slot name="header"></slot>
</div>
<div class="card-body">
<slot></slot>
</div>
<div v-if="$slots.footer" class="card-footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<!-- Usage -->
<Card>
<template #header>Title</template>
<template #default>Content</template>
<template #footer>Actions</template>
</Card>
<!-- Composable Pattern (Vue 3) -->
<script setup>
import { ref, computed } from 'vue';
// useToggle composable
export function useToggle(initialValue = false) {
const value = ref(initialValue);
const toggle = () => { value.value = !value.value; };
return { value, toggle };
}
// Component using composable
const { value: isOpen, toggle } = useToggle(false);
</script>
<!-- Scoped Slots Pattern -->
<template>
<div class="data-fetcher">
<slot :data="data" :loading="loading" :error="error"></slot>
</div>
</template>
<script setup>
const props = defineProps(['url']);
const data = ref(null);
const loading = ref(true);
const error = ref(null);
// Fetch logic...
</script>
<!-- Usage -->
<DataFetcher url="/api/users" v-slot="{ data, loading }">
<Spinner v-if="loading" />
<UserList v-else :users="data" />
</DataFetcher>
// Component with Input/Output Pattern
@Component({
selector: 'app-card',
template: `
<div class="card">
<div class="card-header" *ngIf="header">
<ng-content select="[card-header]"></ng-content>
</div>
<div class="card-body">
<ng-content></ng-content>
</div>
<div class="card-footer" *ngIf="footer">
<ng-content select="[card-footer]"></ng-content>
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CardComponent {
@Input() header: boolean = false;
@Input() footer: boolean = false;
}
// Container/Presentation Pattern
@Component({
selector: 'app-user-list-container',
template: `
<app-user-list-presentation
[users]="users$ | async"
[loading]="loading$ | async"
(userSelected)="onUserSelected($event)">
</app-user-list-presentation>
`
})
export class UserListContainerComponent {
users$ = this.userService.getUsers();
loading$ = this.userService.loading$;
constructor(private userService: UserService) {}
onUserSelected(user: User) {
// Handle user selection logic
}
}
// Smart/Dumb Component Pattern
@Component({
selector: 'app-user-list-presentation',
template: `
<div class="user-list">
<div *ngFor="let user of users"
(click)="userSelected.emit(user)"
class="user-item">
{{ user.name }}
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserListPresentationComponent {
@Input() users: User[] = [];
@Input() loading: boolean = false;
@Output() userSelected = new EventEmitter<User>();
}
[MCP_TOOLS]
<!-- Populated during skill creation based on: 1. User's installed MCP servers 2. User's selection for this skill Recommended for this skill (examples): - Design system integration tools - Component documentation generators - Visual testing platforms Note: Skills work without MCP servers, but functionality may be limited -->// Base components (atoms)
const Button = ({ children, variant, ...props }) => (
<button className={`btn btn-${variant}`} {...props}>
{children}
</button>
);
const Icon = ({ name, size = 16 }) => (
<svg className="icon" width={size} height={size}>
{/* Icon content */}
</svg>
);
const Text = ({ as: Component = 'span', size, weight, children }) => (
<Component className={`text text-${size} font-${weight}`}>
{children}
</Component>
);
// Composite components (molecules)
const IconButton = ({ icon, label, ...props }) => (
<Button {...props}>
<Icon name={icon} />
{label && <Text>{label}</Text>}
</Button>
);
const Card = ({ title, children, actions }) => (
<div className="card">
<Card.Header>
<Text as="h3" size="lg" weight="semibold">{title}</Text>
</Card.Header>
<Card.Body>{children}</Card.Body>
{actions && <Card.Footer>{actions}</Card.Footer>}
</div>
);
// Complex components (organisms)
const UserProfile = ({ user }) => (
<Card
title={user.name}
actions={
<>
<IconButton icon="edit" label="Edit" variant="secondary" />
<IconButton icon="delete" label="Delete" variant="danger" />
</>
}
>
<div className="user-details">
<Text>{user.email}</Text>
<Text size="sm" weight="light">{user.bio}</Text>
</div>
</Card>
);
// Theme context for component styling
const ThemeContext = createContext();
export const ThemeProvider = ({ children, theme = 'light' }) => {
const [currentTheme, setTheme] = useState(theme);
return (
<ThemeContext.Provider value={{ theme: currentTheme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
// Components consume theme
const ThemedButton = ({ children, ...props }) => {
const { theme } = useTheme();
return (
<button className={`btn btn-${theme}`} {...props}>
{children}
</button>
);
};
// Flexible layout component with named slots
const Layout = ({ header, sidebar, footer, children }) => (
<div className="layout">
{header && <div className="layout-header">{header}</div>}
<div className="layout-main">
{sidebar && <aside className="layout-sidebar">{sidebar}</aside>}
<main className="layout-content">{children}</main>
</div>
{footer && <div className="layout-footer">{footer}</div>}
</div>
);
// Usage with component composition
<Layout
header={<Header />}
sidebar={<Navigation />}
footer={<Footer />}
>
<PageContent />
</Layout>
Remember: Good component architecture enables scalability, maintainability, and developer productivity. Focus on composition, reusability, and clear interfaces.
tools
Session Handoff: Erstellt eine vollständige Zusammenfassung der aktuellen Session für einen sauberen Kontextwechsel. NUR bei explizitem Aufruf (/session-handoff). NICHT automatisch auslösen. Geeignet wenn der User die Session resetten will, den Kontext aufräumen will, oder bei ~120k Tokens angelangt ist.
development
Pre-Mortem Risk Analysis: Strukturierte Prospective-Hindsight-Übung um launch-blocking Risiken vor Commitment aufzudecken. Team stellt sich vor, das Produkt sei 14 Tage nach Launch gefloppt, und arbeitet rückwärts. Klassifiziert Risiken in Tigers (echt), Paper Tigers (hypothetisch), Elephants (unausgesprochen). Nutze diesen Skill vor Build-Commitment, bei zu hoher Stakeholder-Confidence, vor Major-Releases, oder wenn das Team vage Sorgen nicht artikulieren kann. Trigger: /pre-mortem, 'pre-mortem', 'risk analysis', 'was könnte schiefgehen', 'risiken vor launch'.
testing
Six-Sigma Atomicity Validator for create-spec stories
tools
UX pattern definition guidance for navigation, user flows, interactions, and accessibility