React Component Design
Purpose
React components MUST be pure, composable, and reusable abstractions. This skill enforces TypeScript-first design, separation of concerns, and deterministic rendering to ensure maintainable, testable UI code that works reliably across different contexts.
When to use
- Building a new UI element from scratch
- Refactoring a large, monolithic React component
- Extracting shared UI patterns into a component library
- Adding new features to an existing component hierarchy
- Preparing components for cross-team reuse
When NOT to use
- Styling implementation details (use DOM Security Hardening skill instead)
- State management architecture decisions (use State Management Patterns skill)
- Performance optimization via memo/useMemo (handle at call site, not in component)
Inputs required
- Existing React codebase with TypeScript
- Clear understanding of component's single responsibility
- Props interface definition (before implementation)
Workflow
- Define the API: Write the TypeScript interface for props BEFORE the component body. This clarifies the contract.
- Verify Props: Ensure props contain ONLY what the component needs—no unnecessary inherited types.
- Isolate Logic: Extract all complex state and side-effects into custom hooks (NEVER in component body).
- Render JSX: Build static JSX structure based solely on props and hook return values.
- Apply Styles: Use CSS classes via
className prop. NEVER use inline style={{...}}.
- Export Type: Export both the component AND its Props interface for consumers.
- Verify Size: Confirm file does NOT exceed 150 lines. If it does, extract sub-components.
Rules
- MUST be a pure function: identical props = identical output
- MUST export a strictly typed TypeScript interface named
[ComponentName]Props
- MUST NOT exceed 150 lines per file (extract sub-components if larger)
- MUST NOT use inline styles (
style={{...}})
- MUST NOT drill props more than 2 levels deep
- MUST NOT fetch data directly (use hooks or parent component)
- MUST support
className prop for customization
Anti-patterns
- Prop Drilling: Passing props down more than 2 levels deep (use Context, composition, or compound components)
- Inline Styles: Using
style={{...}} objects instead of CSS classes
- God Components: Handling data fetching, business logic, and UI rendering in one file
- State in Props: Copying prop values into local state with
useState(props.val)
- Implicit Dependencies: Accessing globals or services without prop parameters
- Class Components: Using class components instead of functional components with hooks
Failure conditions
- Component file exceeds 200 lines
- Props interface is not exported
- Component performs side effects without useEffect
- More than 2 levels of prop drilling detected
Validation checklist
Output format
- File count: 1 primary
.tsx file (+ additional files if refactored)
- Exports: One default export (component) + named export for Props interface
- Structure: Props interface → Component function → Export both
- Styling: CSS classes only, no inline styles
- Size: ≤ 150 lines per component file
Security considerations
- Components do NOT execute arbitrary user input; text content is always escaped
- Props are never used in DOM insertion without sanitization
- Event handlers are never created from strings or user input
- No use of
dangerouslySetInnerHTML (violates DOM Security Hardening)
Agent execution notes
- Agent MAY: Create new components, refactor large components, add TypeScript interfaces
- Agent MUST NEVER: Add inline styles, drill props beyond 2 levels, add data fetching to component
- Agent MUST ASK: Before exceeding 150 lines, before changing prop contracts
- Agent MUST VALIDATE: All TypeScript is strict, no prop drilling, component is pure functional
Example
❌ Anti-pattern (Prop drilling, inline styles, state mirroring):
// Bad: mirrors props, inline styles, multiple responsibilities
export const Modal = ({ title, onClose, userId, userName, isOpen }) => {
const [name, setName] = useState(userName); // ANTI-PATTERN: state from props
return (
<div style={{ position: 'fixed', top: 0 }}
<h1>{title}</h1>
<input value={name} => setName(e.target.value)} />
<UserProfile userId={userId} name={name} /> {/* 2 levels */}
</div>
);
};
export const UserProfile = ({ userId, name }) => {
return <UserBio userId={userId} name={name} />; {/* 3 levels: VIOLATION */}
};
✅ Correct pattern (Pure, CSS classes, no drilling):
interface ModalProps {
title: string;
onClose: () => void;
isOpen: boolean;
children: React.ReactNode;
}
export const Modal = ({ title, onClose, isOpen, children }: ModalProps) => {
if (!isOpen) return null;
return (
<div className="modal-overlay"
<div className="modal-content">
<h1>{title}</h1>
{children}
</div>
</div>
);
};
interface UserProfileProps {
userId: string;
}
export const UserProfile = ({ userId }: UserProfileProps) => {
const user = useUser(userId); // Use hook, not props
return (
<div>
<h2>{user?.name}</h2>
<p>{user?.bio}</p>
</div>
);
};
1---2name: react-component-design3description: When building or refactoring React UI components to ensure reusability and maintainability.4license: MIT5---67# React Component Design89## Purpose10React components MUST be pure, composable, and reusable abstractions. This skill enforces TypeScript-first design, separation of concerns, and deterministic rendering to ensure maintainable, testable UI code that works reliably across different contexts.1112## When to use13- Building a new UI element from scratch14- Refactoring a large, monolithic React component15- Extracting shared UI patterns into a component library16- Adding new features to an existing component hierarchy17- Preparing components for cross-team reuse1819## When NOT to use20- Styling implementation details (use DOM Security Hardening skill instead)21- State management architecture decisions (use State Management Patterns skill)22- Performance optimization via memo/useMemo (handle at call site, not in component)2324## Inputs required25- Existing React codebase with TypeScript26- Clear understanding of component's single responsibility27- Props interface definition (before implementation)2829## Workflow301. **Define the API**: Write the TypeScript interface for props BEFORE the component body. This clarifies the contract.312. **Verify Props**: Ensure props contain ONLY what the component needs—no unnecessary inherited types.323. **Isolate Logic**: Extract all complex state and side-effects into custom hooks (NEVER in component body).334. **Render JSX**: Build static JSX structure based solely on props and hook return values.345. **Apply Styles**: Use CSS classes via `className` prop. NEVER use inline `style={{...}}`.356. **Export Type**: Export both the component AND its Props interface for consumers.367. **Verify Size**: Confirm file does NOT exceed 150 lines. If it does, extract sub-components.3738## Rules39- MUST be a pure function: identical props = identical output40- MUST export a strictly typed TypeScript interface named `[ComponentName]Props`41- MUST NOT exceed 150 lines per file (extract sub-components if larger)42- MUST NOT use inline styles (`style={{...}}`)43- MUST NOT drill props more than 2 levels deep44- MUST NOT fetch data directly (use hooks or parent component)45- MUST support `className` prop for customization4647## Anti-patterns48- **Prop Drilling**: Passing props down more than 2 levels deep (use Context, composition, or compound components)49- **Inline Styles**: Using `style={{...}}` objects instead of CSS classes50- **God Components**: Handling data fetching, business logic, and UI rendering in one file51- **State in Props**: Copying prop values into local state with `useState(props.val)`52- **Implicit Dependencies**: Accessing globals or services without prop parameters53- **Class Components**: Using class components instead of functional components with hooks5455## Failure conditions56- Component file exceeds 200 lines57- Props interface is not exported58- Component performs side effects without useEffect59- More than 2 levels of prop drilling detected6061## Validation checklist62- [ ] Props interface is explicitly exported and named `[ComponentName]Props`63- [ ] Component is a pure function with no render-time side effects64- [ ] No inline `style={{...}}` objects anywhere65- [ ] No prop drilling beyond 2 levels66- [ ] File is ≤ 150 lines67- [ ] All complex logic is extracted to custom hooks68- [ ] Component supports `className` prop for customization69- [ ] TypeScript types are strict (no `any`)70- [ ] No direct API/data fetching in component body7172## Output format73- **File count**: 1 primary `.tsx` file (+ additional files if refactored)74- **Exports**: One default export (component) + named export for Props interface75- **Structure**: Props interface → Component function → Export both76- **Styling**: CSS classes only, no inline styles77- **Size**: ≤ 150 lines per component file7879## Security considerations80- Components do NOT execute arbitrary user input; text content is always escaped81- Props are never used in DOM insertion without sanitization82- Event handlers are never created from strings or user input83- No use of `dangerouslySetInnerHTML` (violates DOM Security Hardening)8485## Agent execution notes86- Agent MAY: Create new components, refactor large components, add TypeScript interfaces87- Agent MUST NEVER: Add inline styles, drill props beyond 2 levels, add data fetching to component88- Agent MUST ASK: Before exceeding 150 lines, before changing prop contracts89- Agent MUST VALIDATE: All TypeScript is strict, no prop drilling, component is pure functional9091## Example9293**❌ Anti-pattern (Prop drilling, inline styles, state mirroring):**94```tsx95// Bad: mirrors props, inline styles, multiple responsibilities96export const Modal = ({ title, onClose, userId, userName, isOpen }) => {97 const [name, setName] = useState(userName); // ANTI-PATTERN: state from props98 99 return (100 <div style={{ position: 'fixed', top: 0 }} onClick={onClose}>101 <h1>{title}</h1>102 <input value={name} onChange={(e) => setName(e.target.value)} />103 <UserProfile userId={userId} name={name} /> {/* 2 levels */}104 </div>105 );106};107108export const UserProfile = ({ userId, name }) => {109 return <UserBio userId={userId} name={name} />; {/* 3 levels: VIOLATION */}110};111```112113**✅ Correct pattern (Pure, CSS classes, no drilling):**114```tsx115interface ModalProps {116 title: string;117 onClose: () => void;118 isOpen: boolean;119 children: React.ReactNode;120}121122export const Modal = ({ title, onClose, isOpen, children }: ModalProps) => {123 if (!isOpen) return null;124 125 return (126 <div className="modal-overlay" onClick={onClose}>127 <div className="modal-content">128 <h1>{title}</h1>129 {children}130 </div>131 </div>132 );133};134135interface UserProfileProps {136 userId: string;137}138139export const UserProfile = ({ userId }: UserProfileProps) => {140 const user = useUser(userId); // Use hook, not props141 return (142 <div>143 <h2>{user?.name}</h2>144 <p>{user?.bio}</p>145 </div>146 );147};148```