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.
Designs TypeScript + React components with type safety and feature-based architecture. Use when planning new features or refactoring to create custom hooks, prevent primitive obsession, and structure code by feature rather than technical layer.
/plugin marketplace add buzzdan/ai-coding-rules/plugin install ts-react-linter-driven-development@ai-coding-rulesThis skill inherits all available tools. When active, it can use any tool Claude has access to.
reference.mdComponent and type design for TypeScript + React applications. Use when planning new features or identifying need for new abstractions during refactoring.
Design clean, well-composed components and types that:
Default: Always use feature-based architecture (group by feature, not technical layer).
Scan codebase structure:
src/features/auth/{LoginForm,useAuth,types,AuthContext}.tsx ✅src/{components,hooks,contexts}/auth.tsx ⚠️Decision Flow:
src/features/[new-feature]/docs/architecture/feature-based-migration.md, implement new feature as first feature sliceAlways ask user approval with options:
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.
Ask for each concept:
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:
Component Types:
A. Presentational Components (Pure UI)
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)
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}
/>
)
}
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 }
}
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
}
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
Check design against (see reference.md):
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.
See reference.md for detailed principles:
Before writing code, ask:
Only after satisfactory answers, proceed to implementation.
See reference.md for complete design principles and examples.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.