
component-designing
by buzzdan
ai coding rules for agents
SKILL.md
name: component-designing description: Component and type design for TypeScript + React code. Use when planning new features, designing components and custom hooks, preventing primitive obsession, or when refactoring reveals need for new abstractions. Focuses on feature-based architecture and type safety.
Component Designing
Component and type design for TypeScript + React applications. Use when planning new features or identifying need for new abstractions during refactoring.
When to Use
- Planning a new feature (before writing code)
- Refactoring reveals need for new components/hooks
- Linter failures suggest better abstractions
- When you need to think through component architecture
- Designing state management approach
Purpose
Design clean, well-composed components and types that:
- Prevent primitive obsession (use branded types, Zod schemas)
- Ensure type safety with TypeScript
- Follow component composition patterns
- Implement feature-based architecture
- Create reusable custom hooks
Workflow
0. Architecture Pattern Analysis (FIRST STEP)
Default: Always use feature-based architecture (group by feature, not technical layer).
Scan codebase structure:
- Feature-based:
src/features/auth/{LoginForm,useAuth,types,AuthContext}.tsx✅ - Technical layers:
src/{components,hooks,contexts}/auth.tsx⚠️
Decision Flow:
- Pure feature-based → Continue pattern, implement as
src/features/[new-feature]/ - Pure technical layers → Propose: Start migration with
docs/architecture/feature-based-migration.md, implement new feature as first feature slice - Mixed (migrating) → Check for migration docs, continue pattern as feature-based
Always ask user approval with options:
- Option A: Feature-based (recommended for cohesion/maintainability)
- Option B: Match existing pattern (if time-constrained)
- Acknowledge: Time pressure, team decisions, consistency needs are valid
If migration needed, create/update docs/architecture/feature-based-migration.md:
# Feature-Based Architecture Migration Plan
## Current State: [technical-layers/mixed]
## Target: Feature-based structure in src/features/[feature]/
## Strategy: New features feature-based, migrate existing incrementally
## Progress: [x] [new-feature] (this PR), [ ] existing features
See reference.md section #2 for detailed patterns.
1. Understand Domain
- What is the problem domain?
- What are the main UI concepts/interactions?
- What state needs to be managed?
- What are the user flows?
- How does this fit into existing architecture?
2. Identify Core Abstractions
Ask for each concept:
- Is this currently a primitive (string, number, boolean)?
- Does it have validation rules?
- Is it a UI concept (component)?
- Is it reusable logic (custom hook)?
- Is it shared state (context)?
- Does it need type safety (branded type)?
3. Design Self-Validating Types
For primitives with validation (Email, UserId, Port):
Option A: Zod Schemas (Recommended)
import { z } from 'zod'
// Schema definition with validation
export const EmailSchema = z.string().email().min(1)
export const UserIdSchema = z.string().uuid()
// Extract type from schema
export type Email = z.infer<typeof EmailSchema>
export type UserId = z.infer<typeof UserIdSchema>
// Validation function
export function validateEmail(value: unknown): Email {
return EmailSchema.parse(value) // Throws on invalid
}
Option B: Branded Types (TypeScript)
// Brand for nominal typing
declare const __brand: unique symbol
type Brand<T, TBrand> = T & { [__brand]: TBrand }
export type Email = Brand<string, 'Email'>
export type UserId = Brand<string, 'UserId'>
// Validating constructor
export function createEmail(value: string): Email {
if (!value.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
throw new Error('Invalid email format')
}
return value as Email
}
export function createUserId(value: string): UserId {
if (!value || value.length === 0) {
throw new Error('UserId cannot be empty')
}
return value as UserId
}
When to use which:
- Zod: Form validation, API parsing, runtime validation
- Branded types: Type safety without runtime overhead
4. Design Component Structure
Component Types:
A. Presentational Components (Pure UI)
- No state management
- Props-driven
- Reusable across features
- 100% testable
interface ButtonProps {
label: string
onClick: () => void
variant?: 'primary' | 'secondary'
disabled?: boolean
}
export function Button({ label, onClick, variant = 'primary', disabled = false }: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{label}
</button>
)
}
B. Container Components (Logic + State)
- Manage state
- Handle side effects
- Coordinate data fetching
- Compose presentational components
export function LoginContainer() {
const { login, isLoading, error } = useAuth()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = async () => {
try {
const validEmail = EmailSchema.parse(email)
await login(validEmail, password)
} catch (error) {
// Handle error
}
}
return (
<LoginForm
email={email}
password={password}
onEmailChange={setEmail}
onPasswordChange={setPassword}
onSubmit={handleSubmit}
isLoading={isLoading}
error={error}
/>
)
}
5. Design Custom Hooks
Extract reusable logic into custom hooks:
// Single responsibility: Form state management
export function useFormState<T>(initialValues: T) {
const [values, setValues] = useState<T>(initialValues)
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({})
const setValue = <K extends keyof T>(key: K, value: T[K]) => {
setValues(prev => ({ ...prev, [key]: value }))
setErrors(prev => ({ ...prev, [key]: undefined }))
}
const reset = () => {
setValues(initialValues)
setErrors({})
}
return { values, errors, setValue, setErrors, reset }
}
// Single responsibility: Data fetching
export function useUsers() {
const [users, setUsers] = useState<User[]>([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
const fetchUsers = async () => {
setIsLoading(true)
try {
const data = await api.getUsers()
setUsers(data)
} catch (err) {
setError(err as Error)
} finally {
setIsLoading(false)
}
}
fetchUsers()
}, [])
return { users, isLoading, error }
}
6. Design Context for Shared State
When state is needed across 3+ component levels:
interface AuthContextValue {
user: User | null
login: (email: Email, password: string) => Promise<void>
logout: () => Promise<void>
isAuthenticated: boolean
}
const AuthContext = createContext<AuthContextValue | null>(null)
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const login = async (email: Email, password: string) => {
const user = await api.login(email, password)
setUser(user)
}
const logout = async () => {
await api.logout()
setUser(null)
}
const value = useMemo(
() => ({ user, login, logout, isAuthenticated: !!user }),
[user]
)
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within AuthProvider')
}
return context
}
7. Plan Feature Structure
Feature-based structure (Recommended):
src/features/auth/
├── components/
│ ├── LoginForm.tsx # Presentational
│ ├── LoginForm.test.tsx
│ ├── RegisterForm.tsx
│ └── RegisterForm.test.tsx
├── hooks/
│ ├── useAuth.ts # Custom hook
│ ├── useAuth.test.ts
│ ├── useFormValidation.ts
│ └── useFormValidation.test.ts
├── context/
│ ├── AuthContext.tsx # Shared state
│ └── AuthContext.test.tsx
├── types.ts # Email, UserId, etc.
├── api.ts # API calls
└── index.ts # Public exports
Bad structure (Technical layers):
src/
├── components/LoginForm.tsx
├── hooks/useAuth.ts
├── contexts/AuthContext.tsx
└── types/auth.ts
8. Review Against Principles
Check design against (see reference.md):
- No primitive obsession (use Zod/branded types)
- Feature-based architecture
- Component composition over prop drilling
- Custom hooks for reusable logic
- Context only when needed (3+ levels)
- Clear separation: presentational vs container
- Props interfaces well-defined
Output Format
After design phase:
🎨 DESIGN PLAN
Feature: User Authentication
Core Domain Types:
✅ Email (Zod schema) - RFC 5322 validation, used in login/register
✅ UserId (branded type) - Non-empty string, prevents invalid IDs
✅ User (interface) - { id: UserId, email: Email, name: string }
Components:
✅ LoginForm (Presentational)
Props: { email, password, onSubmit, isLoading, error }
Responsibility: UI only, no state
✅ LoginContainer (Container)
Responsibility: State management, form handling, validation
Uses: LoginForm, useAuth hook
✅ RegisterForm (Presentational)
Props: { formData, onSubmit, isLoading, errors }
Responsibility: UI only, no state
Custom Hooks:
✅ useAuth
Returns: { user, login, logout, isAuthenticated, isLoading }
Responsibility: Auth operations and state
✅ useFormValidation
Returns: { values, errors, setValue, validate, reset }
Responsibility: Form state and validation logic
Context:
✅ AuthContext
Provides: { user, login, logout, isAuthenticated }
Used by: Protected routes, user menu, profile pages
Reason: Auth state needed across entire app
Feature Structure:
📁 src/features/auth/
├── components/
│ ├── LoginForm.tsx
│ ├── LoginForm.test.tsx
│ ├── RegisterForm.tsx
│ └── RegisterForm.test.tsx
├── hooks/
│ ├── useAuth.ts
│ ├── useAuth.test.ts
│ ├── useFormValidation.ts
│ └── useFormValidation.test.ts
├── context/
│ ├── AuthContext.tsx
│ └── AuthContext.test.tsx
├── types.ts
├── api.ts
└── index.ts
Design Decisions:
- Email and UserId as validated types prevent runtime errors
- Zod for Email (form validation), branded type for UserId (type safety)
- LoginForm is presentational for reusability and testability
- useAuth hook encapsulates auth logic for reuse across components
- AuthContext provides auth state to avoid prop drilling
- Feature-based structure keeps all auth code together
Integration Points:
- Consumed by: App routes, protected route wrapper, user menu
- Depends on: API client, token storage
- Events: User login/logout events for analytics
Next Steps:
1. Create types with validation (Zod schemas + branded types)
2. Write tests for types and hooks (Jest + RTL)
3. Implement presentational components (LoginForm)
4. Implement container components (LoginContainer)
5. Add context provider (AuthContext)
6. Integration tests for full flows
Ready to implement? Use @testing skill for test structure.
Key Principles
See reference.md for detailed principles:
- Primitive obsession prevention (Zod schemas, branded types)
- Component composition patterns
- Feature-based architecture
- Custom hooks for reusable logic
- Context for shared state (use sparingly)
- Props interfaces and type safety
Pre-Code Review Questions
Before writing code, ask:
- Can logic be moved into custom hooks?
- Is this component presentational or container?
- Should state be local or context?
- Have I avoided primitive obsession?
- Is validation in the right place?
- Does this follow feature-based architecture?
- Are components small and focused?
Only after satisfactory answers, proceed to implementation.
See reference.md for complete design principles and examples.
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon