Component Development Skill
Instructions
Analyze Requirements
- What does the component do?
- What props does it need?
- What states does it have?
- Is it reusable or feature-specific?
Determine Location
| Type |
Location |
| Shared UI |
shared/components/ui/ |
| Feature-specific |
features/{feature}/components/ |
| Layout |
shared/components/layouts/ |
Create Component Structure
components/
└── MyComponent/
├── MyComponent.tsx # Main component
├── MyComponent.test.tsx # Tests
└── index.ts # Export
Implement Component
// 1. Imports
import { useState, useCallback } from 'react'
import { cn } from '@/shared/utils/cn'
import type { ComponentProps } from './types'
// 2. Props interface
interface MyComponentProps {
title: string
onAction?: () => void
className?: string
}
// 3. Component
export function MyComponent({
title,
onAction,
className
}: MyComponentProps): React.ReactElement {
const [isActive, setIsActive] = useState(false)
const handleClick = useCallback(() => {
setIsActive(prev => !prev)
onAction?.()
}, [onAction])
return (
<div
className={cn('base-styles', isActive && 'active-styles', className)}
>
<h3>{title}</h3>
</div>
)
}
Write Tests
import { render, screen } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { MyComponent } from './MyComponent'
describe('MyComponent', () => {
it('should render title', () => {
render(<MyComponent title="Test" />)
expect(screen.getByText('Test')).toBeInTheDocument()
})
it('should call onAction when clicked', async () => {
const
const user = userEvent.setup()
render(<MyComponent title="Test" />)
await user.click(screen.getByText('Test'))
expect(onAction).toHaveBeenCalled()
})
})
Export from Index
// components/index.ts
export { MyComponent } from "./MyComponent";
export type { MyComponentProps } from "./MyComponent";
Component Templates
Presentational Component
interface CardProps {
title: string
description?: string
children?: React.ReactNode
className?: string
}
export function Card({
title,
description,
children,
className
}: CardProps): React.ReactElement {
return (
<div className={cn('rounded-lg border p-4', className)}>
<h3 className="font-semibold">{title}</h3>
{description && <p className="text-muted">{description}</p>}
{children}
</div>
)
}
Interactive Component
interface ButtonProps {
children: React.ReactNode
variant?: 'primary' | 'secondary' | 'ghost'
size?: 'sm' | 'md' | 'lg'
loading?: boolean
disabled?: boolean
onClick?: () => void
className?: string
}
export function Button({
children,
variant = 'primary',
size = 'md',
loading = false,
disabled = false,
onClick,
className,
}: ButtonProps): React.ReactElement {
return (
<button
className={cn(
'rounded font-medium transition-colors',
variantStyles[variant],
sizeStyles[size],
disabled && 'opacity-50 cursor-not-allowed',
className
)}
disabled={disabled || loading}
>
{loading ? <Spinner /> : children}
</button>
)
}
Form Component
interface FormInputProps {
label: string
name: string
type?: 'text' | 'email' | 'password'
error?: string
register: UseFormRegister<any>
}
export function FormInput({
label,
name,
type = 'text',
error,
register,
}: FormInputProps): React.ReactElement {
return (
<div className="space-y-1">
<label htmlFor={name} className="text-sm font-medium">
{label}
</label>
<input
id={name}
type={type}
{...register(name)}
className={cn(
'w-full rounded border px-3 py-2',
error && 'border-red-500'
)}
/>
{error && <p className="text-sm text-red-500">{error}</p>}
</div>
)
}
Checklist
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: react-component3description: Create React components following project standards. Use when building new UI components, creating reusable components, or implementing designs. Use when this capability is needed.4---56# Component Development Skill78## Instructions9101. **Analyze Requirements**11 - What does the component do?12 - What props does it need?13 - What states does it have?14 - Is it reusable or feature-specific?15162. **Determine Location**1718 | Type | Location |19 | ---------------- | -------------------------------- |20 | Shared UI | `shared/components/ui/` |21 | Feature-specific | `features/{feature}/components/` |22 | Layout | `shared/components/layouts/` |23243. **Create Component Structure**2526 ```27 components/28 └── MyComponent/29 ├── MyComponent.tsx # Main component30 ├── MyComponent.test.tsx # Tests31 └── index.ts # Export32 ```33344. **Implement Component**3536 ```typescript37 // 1. Imports38 import { useState, useCallback } from 'react'39 import { cn } from '@/shared/utils/cn'40 import type { ComponentProps } from './types'4142 // 2. Props interface43 interface MyComponentProps {44 title: string45 onAction?: () => void46 className?: string47 }4849 // 3. Component50 export function MyComponent({51 title,52 onAction,53 className54 }: MyComponentProps): React.ReactElement {55 const [isActive, setIsActive] = useState(false)5657 const handleClick = useCallback(() => {58 setIsActive(prev => !prev)59 onAction?.()60 }, [onAction])6162 return (63 <div64 className={cn('base-styles', isActive && 'active-styles', className)}65 onClick={handleClick}66 >67 <h3>{title}</h3>68 </div>69 )70 }71 ```72735. **Write Tests**7475 ```typescript76 import { render, screen } from '@testing-library/react'77 import { userEvent } from '@testing-library/user-event'78 import { MyComponent } from './MyComponent'7980 describe('MyComponent', () => {81 it('should render title', () => {82 render(<MyComponent title="Test" />)83 expect(screen.getByText('Test')).toBeInTheDocument()84 })8586 it('should call onAction when clicked', async () => {87 const onAction = vi.fn()88 const user = userEvent.setup()8990 render(<MyComponent title="Test" onAction={onAction} />)91 await user.click(screen.getByText('Test'))9293 expect(onAction).toHaveBeenCalled()94 })95 })96 ```97986. **Export from Index**99100 ```typescript101 // components/index.ts102 export { MyComponent } from "./MyComponent";103 export type { MyComponentProps } from "./MyComponent";104 ```105106## Component Templates107108### Presentational Component109110```typescript111interface CardProps {112 title: string113 description?: string114 children?: React.ReactNode115 className?: string116}117118export function Card({119 title,120 description,121 children,122 className123}: CardProps): React.ReactElement {124 return (125 <div className={cn('rounded-lg border p-4', className)}>126 <h3 className="font-semibold">{title}</h3>127 {description && <p className="text-muted">{description}</p>}128 {children}129 </div>130 )131}132```133134### Interactive Component135136```typescript137interface ButtonProps {138 children: React.ReactNode139 variant?: 'primary' | 'secondary' | 'ghost'140 size?: 'sm' | 'md' | 'lg'141 loading?: boolean142 disabled?: boolean143 onClick?: () => void144 className?: string145}146147export function Button({148 children,149 variant = 'primary',150 size = 'md',151 loading = false,152 disabled = false,153 onClick,154 className,155}: ButtonProps): React.ReactElement {156 return (157 <button158 className={cn(159 'rounded font-medium transition-colors',160 variantStyles[variant],161 sizeStyles[size],162 disabled && 'opacity-50 cursor-not-allowed',163 className164 )}165 disabled={disabled || loading}166 onClick={onClick}167 >168 {loading ? <Spinner /> : children}169 </button>170 )171}172```173174### Form Component175176```typescript177interface FormInputProps {178 label: string179 name: string180 type?: 'text' | 'email' | 'password'181 error?: string182 register: UseFormRegister<any>183}184185export function FormInput({186 label,187 name,188 type = 'text',189 error,190 register,191}: FormInputProps): React.ReactElement {192 return (193 <div className="space-y-1">194 <label htmlFor={name} className="text-sm font-medium">195 {label}196 </label>197 <input198 id={name}199 type={type}200 {...register(name)}201 className={cn(202 'w-full rounded border px-3 py-2',203 error && 'border-red-500'204 )}205 />206 {error && <p className="text-sm text-red-500">{error}</p>}207 </div>208 )209}210```211212## Checklist213214- [ ] Props interface defined215- [ ] TypeScript types correct216- [ ] Styles use cn() helper217- [ ] className prop for customization218- [ ] Accessibility (aria labels, roles)219- [ ] Tests written220- [ ] Exported from index221222---223> Converted and distributed by [TomeVault](https://tomevault.io/claim/ademkao) — claim your Tome and manage your conversions.224<!-- tomevault:4.0:skill_md:2026-04-15 -->