| name | code-review |
| description | Frontend-focused code review skill for React/TypeScript/Tailwind projects. Analyzes code quality, security vulnerabilities (XSS, CSRF), performance issues, accessibility (WCAG), React best practices, hooks usage, component architecture, responsive design, and SEO. Use when users request code review, want feedback on components, ask about frontend security, performance optimization, or accessibility compliance. Provides actionable feedback with severity levels and fix suggestions. |
| allowed-tools | Read, Grep, Glob, Bash |
Frontend Code Review
This skill provides comprehensive, production-ready code review for modern frontend applications with actionable feedback focused on React/TypeScript/Tailwind stack.
Purpose
Transform frontend code review from manual inspection into systematic analysis covering:
- Frontend Security - XSS, CSRF, sensitive data exposure, auth issues
- React Performance - Re-renders, memoization, bundle size, lazy loading
- Code Quality - Readability, maintainability, React best practices
- Component Architecture - Layered architecture, separation of concerns, reusability
- Type Safety - TypeScript usage, type correctness, runtime validation
- Accessibility - WCAG compliance, keyboard navigation, screen readers
- Responsive Design - Mobile-first, breakpoints, Tailwind patterns
- SEO & Meta - Meta tags, semantic HTML, performance metrics
- Testing - Component tests, hooks tests, edge cases
- State Management - Zustand/Context patterns, React Query usage
When to Use This Skill
Use this skill when:
- User asks for code review or feedback
- User mentions: "review", "check", "feedback", "quality", "security"
- After generating components or features
- User asks about performance or accessibility
- Before committing major changes
- Examples:
- "Review this component"
- "Is this React code optimized?"
- "Can you check for accessibility issues?"
- "How can I improve this?"
- "Review my feature implementation"
Review Process
Step 1: Understand Context
Before reviewing, gather context:
Code Type:
- React Component (UI, Form, List, etc.)
- Custom Hook (business logic)
- Utility function (helpers, transforms)
- API integration (React Query, fetch)
- Store/State management (Zustand, Context)
- Styling (Tailwind, CSS-in-JS)
Review Scope:
- Single component/hook
- Entire feature (multiple files)
- Page/route implementation
- Shared utilities
Priority:
- Security-critical (auth, payment forms)
- Performance-critical (large lists, complex calculations)
- User-facing (accessibility, UX)
- Internal (utilities, helpers)
Step 2: Initial Scan
Quickly scan for obvious issues:
Critical Issues (🚨 CRITICAL):
- XSS vulnerabilities (dangerouslySetInnerHTML)
- CSRF vulnerabilities (missing tokens)
- Sensitive data exposure (tokens in localStorage)
- Authentication bypass
- Hardcoded secrets/API keys
High Priority (⚠️ HIGH):
- Performance bottlenecks (unnecessary re-renders, no memoization)
- Memory leaks (missing cleanup in useEffect)
- Error handling gaps
- Accessibility violations (no ARIA labels, keyboard support)
- Missing input validation
Medium Priority (⚡ MEDIUM):
- Code duplication
- Unclear component/variable names
- Missing loading/error states
- Poor TypeScript usage (any types)
- Inconsistent Tailwind usage
Low Priority (💡 LOW):
- Code style inconsistencies
- Missing comments for complex logic
- Minor optimizations
- Documentation gaps
Step 3: Deep Analysis
Perform systematic review across all dimensions:
3.1 Frontend Security Review
Check against common frontend vulnerabilities:
// ❌ BAD: XSS vulnerability
function UserComment({ comment }: { comment: string }) {
return <div dangerouslySetInnerHTML={{ __html: comment }} />;
}
// ✅ GOOD: Sanitized HTML
import DOMPurify from 'dompurify';
function UserComment({ comment }: { comment: string }) {
return <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(comment) }} />;
}
// ✅ BETTER: No HTML, just text
function UserComment({ comment }: { comment: string }) {
return <div>{comment}</div>;
}
// ❌ BAD: Token in localStorage (XSS vulnerable)
localStorage.setItem('token', response.token);
// ✅ GOOD: HttpOnly cookie (set by server)
// Or use secure session management library
// ❌ BAD: Hardcoded API key
const API_KEY = "pk_live_abc123xyz";
// ✅ GOOD: Environment variable
const API_KEY = process.env.NEXT_PUBLIC_API_KEY;
// ❌ BAD: No CSRF protection
async function transferMoney(to: string, amount: number) {
await fetch('/api/transfer', {
method: 'POST',
body: JSON.stringify({ to, amount })
});
}
// ✅ GOOD: Include CSRF token
async function transferMoney(to: string, amount: number) {
const csrfToken = getCsrfToken();
await fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ to, amount })
});
}
Frontend Security Checklist:
- No
dangerouslySetInnerHTMLwith user input - No sensitive tokens in localStorage
- No hardcoded API keys or secrets
- CSRF protection for state-changing operations
- Input validation on all form inputs
- Output sanitization for user-generated content
- HTTPS enforced (check in production)
- No sensitive data in console.log
- Proper authentication checks on protected routes
- Secure password input (no autocomplete on sensitive fields)
3.2 React Performance Review
Identify bottlenecks and optimization opportunities:
// ❌ BAD: Unnecessary re-renders
function ProductList({ products }: { products: Product[] }) {
const sortedProducts = products.sort((a, b) => b.price - a.price);
// Re-sorts on every render!
return (
<div>
{sortedProducts.map(p => (
<ProductCard key={p.id} product={p} onUpdate={() => updateProduct(p.id)} />
))}
</div>
);
}
// ✅ GOOD: Memoized sorting and callbacks
function ProductList({ products }: { products: Product[] }) {
const sortedProducts = useMemo(
() => [...products].sort((a, b) => b.price - a.price),
[products]
);
const handleUpdate = useCallback((id: string) => {
updateProduct(id);
}, []);
return (
<div>
{sortedProducts.map(p => (
<ProductCard key={p.id} product={p} onUpdate={() => handleUpdate(p.id)} />
))}
</div>
);
}
// ❌ BAD: No memoization for expensive child
function ExpensiveChild({ data }: { data: Data }) {
// Complex rendering logic
return <div>{/* ... */}</div>;
}
// ✅ GOOD: Memoized component
const ExpensiveChild = memo(function ExpensiveChild({ data }: { data: Data }) {
// Complex rendering logic
return <div>{/* ... */}</div>;
});
// ❌ BAD: Memory leak - no cleanup
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// No cleanup!
}, []);
return <div>{count}</div>;
}
// ✅ GOOD: Proper cleanup
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>{count}</div>;
}
// ❌ BAD: No lazy loading for large components
import HeavyChart from './HeavyChart';
import HeavyEditor from './HeavyEditor';
// ✅ GOOD: Lazy loading
const HeavyChart = lazy(() => import('./HeavyChart'));
const HeavyEditor = lazy(() => import('./HeavyEditor'));
function Dashboard() {
return (
<Suspense fallback={<Spinner />}>
<HeavyChart />
<HeavyEditor />
</Suspense>
);
}
// ❌ BAD: Inline object/function props
<Child
config={{ theme: 'dark' }}
onUpdate={() => doSomething()}
/>
// ✅ GOOD: Memoized props
const config = useMemo(() => ({ theme: 'dark' }), []);
const handleUpdate = useCallback(() => doSomething(), []);
<Child config={config} onUpdate={handleUpdate} />
React Performance Checklist:
- Memoization for expensive calculations (useMemo)
- Memoized callbacks (useCallback) for child components
- memo() for expensive child components
- Proper cleanup in useEffect (intervals, subscriptions)
- Lazy loading for heavy components
- Code splitting for routes
- Virtualization for long lists (react-window)
- Debouncing for frequent events (search, scroll)
- Image optimization (next/image, lazy loading)
- No inline objects/functions as props
- Proper key usage (unique IDs, not indexes)
- Avoid deep prop drilling (use Context/Zustand)
3.3 Component Architecture Review
Check for proper separation of concerns:
// ❌ BAD: Everything in one component
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// API call in component
useEffect(() => {
setLoading(true);
fetch('/api/user')
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
// Business logic in component
const fullName = user ? `${user.firstName} ${user.lastName}` : '';
const isAdult = user?.age >= 18;
// Validation in component
const validateEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{fullName}</div>;
}
// ✅ GOOD: Layered architecture
// api/userApi.ts (Data Access Layer)
export const userApi = {
getUser: async (): Promise<User> => {
const response = await fetch('/api/user');
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
}
};
// hooks/useUser.ts (Business Logic Layer)
export function useUser() {
const { data: user, isLoading, error } = useQuery({
queryKey: ['user'],
queryFn: userApi.getUser
});
const fullName = user ? `${user.firstName} ${user.lastName}` : '';
const isAdult = user?.age >= 18;
return { user, fullName, isAdult, isLoading, error };
}
// utils/validation.ts (Business Logic Layer)
export const emailSchema = z.string().email();
export function validateEmail(email: string): boolean {
return emailSchema.safeParse(email).success;
}
// components/UserProfile.tsx (Presentation Layer)
export function UserProfile() {
const { fullName, isLoading, error } = useUser();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return <div className="text-lg font-semibold">{fullName}</div>;
}
Architecture Checklist:
- Layered architecture (Presentation, Business Logic, Data Access)
- No API calls directly in components
- No business logic in components
- Hooks for reusable logic
- Zustand/Context for global state
- React Query for server state
- No circular dependencies
- Single Responsibility Principle
- Components are small and focused (<200 lines)
- Proper prop types (TypeScript interfaces)
3.4 Type Safety Review
Check for proper TypeScript usage:
// ❌ BAD: Using 'any'
function processData(data: any) {
return data.value;
}
// ✅ GOOD: Proper types
interface ProcessData {
value: string;
count: number;
}
function processData(data: ProcessData): string {
return data.value;
}
// ❌ BAD: Non-null assertion without justification
function getUser(users: User[], id: string) {
return users.find(u => u.id === id)!.name; // Dangerous!
}
// ✅ GOOD: Proper null handling
function getUser(users: User[], id: string): string | null {
return users.find(u => u.id === id)?.name ?? null;
}
// ❌ BAD: No runtime validation
function LoginForm() {
const handleSubmit = (data: unknown) => {
// Assuming data structure without validation
login(data);
};
}
// ✅ GOOD: Runtime validation with Zod
import { z } from 'zod';
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8)
});
type LoginData = z.infer<typeof loginSchema>;
function LoginForm() {
const handleSubmit = (data: unknown) => {
try {
const validatedData = loginSchema.parse(data);
login(validatedData); // Type-safe
} catch (err) {
// Handle validation errors
}
};
}
// ❌ BAD: Implicit any in event handlers
<input onChange={(e) => setValue(e.target.value)} />
// 'e' is implicitly 'any' in some configs
// ✅ GOOD: Explicit types
<input onChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value)} />
// ✅ BETTER: Typed handler
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
<input onChange={handleChange} />
Type Safety Checklist:
- No 'any' types (except where truly necessary)
- No non-null assertions (!) without justification
- Proper interface/type definitions for props
- Runtime validation with Zod for external data
- Type guards for discriminated unions
- Explicit types for event handlers
- Strict mode enabled in tsconfig.json
- No implicit any
- Proper generic usage in hooks
3.5 Accessibility Review
Check WCAG 2.1 AA compliance:
// ❌ BAD: No keyboard support, no ARIA labels
<div onClick={handleClick}>
<input type="text" placeholder="Name" />
<div className="text-red-500">Error message</div>
</div>
// ✅ GOOD: Semantic HTML, ARIA labels, keyboard support
<form onSubmit={handleSubmit}>
<label htmlFor="name" className="block text-sm font-medium">
Name
</label>
<input
id="name"
type="text"
aria-label="Enter your name"
aria-describedby="name-error"
aria-invalid={hasError}
className="mt-1 block w-full"
/>
{hasError && (
<p id="name-error" role="alert" className="text-red-500 text-sm">
Error message
</p>
)}
<button type="submit" className="mt-4 px-4 py-2 bg-blue-500">
Submit
</button>
</form>
// ❌ BAD: No alt text, poor color contrast
<div className="bg-gray-200 text-gray-300">
<img src="/icon.png" />
Click here
</div>
// ✅ GOOD: Alt text, proper contrast (WCAG AA)
<div className="bg-gray-900 text-white">
<img src="/icon.png" alt="Settings icon" />
<button className="text-lg font-medium">
Open Settings
</button>
</div>
// ❌ BAD: Custom select without keyboard navigation
function CustomSelect({ options }) {
const [open, setOpen] = useState(false);
return (
<div onClick={() => setOpen(!open)}>
{open && options.map(opt => (
<div onClick={() => selectOption(opt)}>{opt}</div>
))}
</div>
);
}
// ✅ GOOD: Accessible custom select
function CustomSelect({ options }: { options: string[] }) {
const [open, setOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowDown') {
setSelectedIndex(i => Math.min(i + 1, options.length - 1));
} else if (e.key === 'ArrowUp') {
setSelectedIndex(i => Math.max(i - 1, 0));
} else if (e.key === 'Enter') {
selectOption(options[selectedIndex]);
}
};
return (
<div
role="combobox"
aria-expanded={open}
aria-haspopup="listbox"
tabIndex={0}
onKeyDown={handleKeyDown}
onClick={() => setOpen(!open)}
>
{open && (
<ul role="listbox">
{options.map((opt, index) => (
<li
key={opt}
role="option"
aria-selected={index === selectedIndex}
onClick={() => selectOption(opt)}
>
{opt}
</li>
))}
</ul>
)}
</div>
);
}
Accessibility Checklist:
- Semantic HTML elements (button, nav, main, etc.)
- ARIA labels for interactive elements
- Keyboard navigation support (Tab, Enter, Arrows)
- Focus management (visible focus states)
- Color contrast meets WCAG AA (4.5:1 for text)
- Alt text for all images
- Form labels properly associated (htmlFor)
- Error messages use role="alert"
- No keyboard traps
- Skip links for main content
- Headings in logical order (h1, h2, h3)
- Interactive elements have proper roles
3.6 Responsive Design Review
Check Tailwind/responsive patterns:
// ❌ BAD: Fixed widths, no responsive classes
<div className="w-800 h-600">
<img src="/hero.jpg" className="w-full" />
</div>
// ✅ GOOD: Responsive Tailwind classes
<div className="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<img
src="/hero.jpg"
alt="Hero"
className="w-full h-auto object-cover md:h-96 lg:h-[500px]"
/>
</div>
// ❌ BAD: No mobile considerations
<div className="flex gap-8">
<Sidebar />
<MainContent />
</div>
// ✅ GOOD: Mobile-first responsive layout
<div className="flex flex-col lg:flex-row gap-4 lg:gap-8">
<aside className="w-full lg:w-64">
<Sidebar />
</aside>
<main className="flex-1">
<MainContent />
</main>
</div>
// ❌ BAD: Fixed font sizes
<h1 className="text-32">Title</h1>
// ✅ GOOD: Responsive typography
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold">
Title
</h1>
Responsive Design Checklist:
- Mobile-first approach (base styles for mobile)
- Proper breakpoints (sm, md, lg, xl, 2xl)
- Flexible layouts (flex, grid)
- Responsive images (w-full h-auto, object-fit)
- Responsive typography (responsive text sizes)
- Touch-friendly hit areas (min 44x44px)
- No horizontal scrolling on mobile
- Tested on multiple screen sizes
3.7 Code Quality Review
Evaluate readability and maintainability:
// ❌ BAD: Unclear names, deeply nested
function p(d) {
if (d) {
if (d.u) {
if (d.u.n) {
if (d.u.n.length > 0) {
return d.u.n;
}
}
}
}
return 'Anonymous';
}
// ✅ GOOD: Clear names, early returns
function getUserName(data: UserData | null): string {
if (!data?.user?.name) return 'Anonymous';
if (data.user.name.length === 0) return 'Anonymous';
return data.user.name;
}
// ✅ BETTER: Optional chaining
function getUserName(data: UserData | null): string {
return data?.user?.name || 'Anonymous';
}
// ❌ BAD: Magic numbers and strings
if (user.age > 18 && status === 'active') {
grantAccess();
}
// ✅ GOOD: Named constants
const MINIMUM_AGE = 18;
const USER_STATUS = {
ACTIVE: 'active',
INACTIVE: 'inactive'
} as const;
if (user.age > MINIMUM_AGE && status === USER_STATUS.ACTIVE) {
grantAccess();
}
// ❌ BAD: Long component with multiple responsibilities
function UserDashboard() {
// 300 lines of code handling:
// - User data fetching
// - Analytics tracking
// - Notification handling
// - UI rendering
}
// ✅ GOOD: Split into focused components
function UserDashboard() {
return (
<div>
<UserProfile />
<UserAnalytics />
<UserNotifications />
</div>
);
}
Code Quality Checklist:
- Clear, descriptive names (getUserName, not gn)
- No magic numbers or strings (use constants)
- Early returns instead of deep nesting
- Small functions/components (<100 lines)
- Single Responsibility Principle
- DRY (Don't Repeat Yourself)
- No commented-out code
- No console.log in production code
- Consistent code style
- Proper error handling
3.8 React Hooks Best Practices
// ❌ BAD: Missing dependencies
useEffect(() => {
fetchData(userId, filter);
}, []); // userId and filter are missing!
// ✅ GOOD: All dependencies included
useEffect(() => {
fetchData(userId, filter);
}, [userId, filter]);
// ❌ BAD: Object dependency (recreated every render)
const options = { sort: 'asc', limit: 10 };
useEffect(() => {
fetchData(options);
}, [options]); // Will run every render!
// ✅ GOOD: Memoized object
const options = useMemo(() => ({
sort: 'asc',
limit: 10
}), []);
useEffect(() => {
fetchData(options);
}, [options]);
// ❌ BAD: Stale closure
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1); // Uses stale count!
}, 1000);
return () => clearInterval(interval);
}, []); // Empty deps, count is stale
return <div>{count}</div>;
}
// ✅ GOOD: Functional update
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1); // Uses current count
}, 1000);
return () => clearInterval(interval);
}, []); // Empty deps OK now
return <div>{count}</div>;
}
Step 4: Provide Structured Feedback
Format review results clearly:
## Code Review: [Component/Feature Name]
### Summary
[Brief overall assessment: Excellent/Good/Needs Improvement/Critical Issues]
### Critical Issues 🚨
1. **[Issue Title]** - [file:line]
- **Problem**: [Description]
- **Impact**: [Security/Performance/Accessibility]
- **Fix**:
```typescript
// Suggested fix
High Priority ⚠️
[Same format]
Medium Priority ⚡
[Same format]
Low Priority 💡
[Same format]
What's Good ✅
- [Highlight positive aspects]
- [Good practices used]
Recommendations
- [Actionable improvement]
- [Actionable improvement]
Overall Score
- Security: 8/10
- Performance: 7/10
- Code Quality: 9/10
- Accessibility: 6/10
- Type Safety: 8/10
Overall: 7.6/10
## Integration with Other Skills
### With feature-builder
- Review complete features (UI + business logic + API)
- Ensure layered architecture is properly implemented
### With react-component-generator
- Review generated components
- Check template usage and customization
### With ui-analyzer
- Review UI code generated from design screenshots
- Verify responsive design implementation
## Best Practices
1. **Be Constructive**: Always provide actionable fixes with code examples
2. **Prioritize**: Focus on critical issues (security, accessibility) first
3. **Explain Why**: Help user understand the reasoning and impact
4. **Show Before/After**: Provide clear code examples
5. **Be Specific**: Reference exact lines and files
6. **Balance**: Highlight what's good too
7. **Be Practical**: Consider project constraints and deadlines
8. **Educate**: Explain React/frontend concepts when needed
## Severity Levels
- 🚨 **CRITICAL**: Security vulnerabilities (XSS, CSRF), data exposure, crashes
- ⚠️ **HIGH**: Performance issues (memory leaks), accessibility violations, broken UX
- ⚡ **MEDIUM**: Code quality, maintainability, missing TypeScript types
- 💡 **LOW**: Code style, documentation, minor optimizations
## Reference Files
- `references/frontend-security.md` - Frontend security best practices
- `references/react-patterns.md` - React patterns and anti-patterns
- `references/accessibility-guide.md` - WCAG compliance guide
This skill enables thorough, professional frontend code reviews that improve code quality, security, and user experience!