code-review について
code-review は、React、TypeScript、Tailwind を使うフロントエンド向けのコードレビュー用スキルです。セキュリティ、性能、設計、型、アクセシビリティ、テストを確認し、重要度別の指摘と改善案をまとめます。
主な機能
- 01フロントエンドの安全性と入力検証の確認
- 02React の性能とコンポーネント設計の評価
- 03TypeScript の型安全性のレビュー
- 04アクセシビリティとレスポンシブ対応の確認
- 05指摘事項と提案の優先順位付け
活用シーン
- 01コミット前に主要機能の変更をレビュー
- 02コンポーネントの安全性と性能を確認
- 03ページのアクセシビリティや型の問題を特定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ smallnest/ langgraphgo --skill code-reviewインストール後、Claude Code で/code-reviewを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
使用说明.mdcode-review/resources/FEEDBACK-GUIDELINES.mdREVIEW-CHECKLIST.mdreferences/accessibility-guide.mdfrontend-security.mdreact-patterns.mdsecurity-checklist.md詳細
フロントエンドコードレビュー
このスキルは、React/TypeScript/Tailwind スタックを中心に、実用的なフィードバックを提供する、包括的で本番環境に対応したフロントエンドコードレビューを行います。
目的
フロントエンドのコードレビューを手動での確認から、次の項目を対象とする体系的な分析へと変えます。
- フロントエンドセキュリティ - XSS、CSRF、機密データの露出、認証の問題
- React のパフォーマンス - 再レンダリング、メモ化、バンドルサイズ、遅延読み込み
- コード品質 - 読みやすさ、保守性、React のベストプラクティス
- コンポーネントアーキテクチャ - レイヤードアーキテクチャ、関心の分離、再利用性
- 型安全性 - TypeScript の使用、型の正確性、実行時検証
- アクセシビリティ - WCAG 準拠、キーボード操作、スクリーンリーダー
- レスポンシブデザイン - モバイルファースト、ブレークポイント、Tailwind のパターン
- SEO とメタ情報 - メタタグ、セマンティック HTML、パフォーマンス指標
- テスト - コンポーネントテスト、フックのテスト、エッジケース
- 状態管理 - Zustand/Context のパターン、React Query の使用
このスキルを使う場面
次のような場合にこのスキルを使います。
- ユーザーがコードレビューやフィードバックを求めている
- ユーザーが「レビュー」「確認」「フィードバック」「品質」「セキュリティ」に言及している
- コンポーネントや機能を生成した後
- ユーザーがパフォーマンスやアクセシビリティについて尋ねている
- 大規模な変更をコミットする前
- 例:
- 「このコンポーネントをレビューしてください」
- 「この React コードは最適化されていますか?」
- 「アクセシビリティの問題がないか確認してもらえますか?」
- 「どうすれば改善できますか?」
- 「この機能の実装をレビューしてください」
レビュープロセス
ステップ 1:コンテキストを把握する
レビューを始める前に、コンテキストを確認します。
-
コードの種類:
- React コンポーネント(UI、フォーム、リストなど)
- カスタムフック(ビジネスロジック)
- ユーティリティ関数(ヘルパー、変換処理)
- API 連携(React Query、fetch)
- ストア/状態管理(Zustand、Context)
- スタイリング(Tailwind、CSS-in-JS)
-
レビューの範囲:
- 単一のコンポーネント/フック
- 機能全体(複数のファイル)
- ページ/ルートの実装
- 共有ユーティリティ
-
優先度:
- セキュリティが重要(認証、決済フォーム)
- パフォーマンスが重要(大規模なリスト、複雑な計算)
- ユーザー向け(アクセシビリティ、UX)
- 内部向け(ユーティリティ、ヘルパー)
ステップ 2:初期スキャン
明らかな問題がないか、すばやく確認します。
重大な問題(🚨 CRITICAL):
- XSS の脆弱性(dangerouslySetInnerHTML)
- CSRF の脆弱性(トークンの欠落)
- 機密データの露出(localStorage 内のトークン)
- 認証の回避
- ハードコードされたシークレット/API キー
優先度:高(⚠️ HIGH):
- パフォーマンスのボトルネック(不要な再レンダリング、メモ化の欠如)
- メモリリーク(useEffect 内のクリーンアップ漏れ)
- エラーハンドリングの不足
- アクセシビリティ違反(ARIA ラベルやキーボード操作の欠如)
- 入力検証の欠如
優先度:中(⚡ MEDIUM):
- コードの重複
- コンポーネント名/変数名が不明瞭
- ローディング状態/エラー状態の欠如
- TypeScript の使い方が不適切(any 型)
- Tailwind の使い方に一貫性がない
優先度:低(💡 LOW):
- コードスタイルの不整合
- 複雑なロジックに対するコメントの欠如
- 軽微な最適化
- ドキュメントの不足
ステップ 3:詳細分析
すべての観点から、体系的にレビューを行います。
3.1 フロントエンドセキュリティレビュー
一般的なフロントエンドの脆弱性がないか確認します。
// ❌ 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 })
});
}
フロントエンドセキュリティチェックリスト:
- ユーザー入力を含む
dangerouslySetInnerHTMLを使用していない - localStorage に機密トークンを保存していない
- API キーやシークレットをハードコードしていない
- 状態を変更する操作に CSRF 対策を実装している
- すべてのフォーム入力を検証している
- ユーザー生成コンテンツの出力をサニタイズしている
- HTTPS を強制している(本番環境で確認)
- console.log に機密データを出力していない
- 保護されたルートで適切な認証チェックを行っている
- パスワード入力を安全に処理している(機密フィールドでは autocomplete を無効にする)
3.2 React のパフォーマンスレビュー
ボトルネックと最適化の機会を特定します:
// ❌ 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 パフォーマンスチェックリスト:
- コストの高い計算をメモ化する(useMemo)
- 子コンポーネント向けのコールバックをメモ化する(useCallback)
- コストの高い子コンポーネントに memo() を使用する
- useEffect で適切にクリーンアップする(インターバル、サブスクリプション)
- 負荷の高いコンポーネントを遅延読み込みする
- ルートをコード分割する
- 長いリストを仮想化する(react-window)
- 頻繁に発生するイベント(検索、スクロール)にデバウンスを適用する
- 画像を最適化する(next/image、遅延読み込み)
- props にインラインのオブジェクトや関数を渡さない
- key を適切に使用する(インデックスではなく一意の ID)
- 深い props のバケツリレーを避ける(Context/Zustand を使用)
3.3 コンポーネントアーキテクチャのレビュー
関心事が適切に分離されているか確認します:
// ❌ 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>;
}
アーキテクチャチェックリスト:
- レイヤードアーキテクチャ(プレゼンテーション、ビジネスロジック、データアクセス)
- コンポーネント内で API を直接呼び出していない
- コンポーネント内にビジネスロジックがない
- 再利用可能なロジックにはフックを使用する
- グローバル状態には Zustand/Context を使用する
- サーバー状態には React Query を使用する
- 循環依存がない
- 単一責任の原則
- コンポーネントが小さく、役割が明確である(200 行未満)
- props の型が適切である(TypeScript のインターフェース)
3.4 型安全性のレビュー
TypeScript が適切に使用されているか確認します:
// ❌ 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} />
型安全性チェックリスト:
- 'any' 型を使用していない(本当に必要な場合を除く)
- 正当な理由なく非 null アサーション(!)を使用していない
- props に適切な interface/type 定義がある
- 外部データを Zod で実行時検証する
- 判別可能なユニオンに型ガードを使用する
- イベントハンドラーに明示的な型を指定する
- tsconfig.json で strict モードが有効になっている
- 暗黙的な any がない
- フックでジェネリクスを適切に使用する
3.5 アクセシビリティのレビュー
WCAG 2.1 AA への準拠を確認します:
// ❌ 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>
);
}
アクセシビリティチェックリスト:
- セマンティックな HTML 要素(button、nav、main など)
- インタラクティブ要素に ARIA ラベルがある
- キーボード操作に対応している(Tab、Enter、矢印キー)
- フォーカスを管理する(フォーカス状態を視認できる)
- 色のコントラストが WCAG AA を満たしている(テキストは 4.5:1)
- すべての画像に代替テキストがある
- フォームのラベルが適切に関連付けられている(htmlFor)
- エラーメッセージで role="alert" を使用する
- キーボードトラップがない
- メインコンテンツへのスキップリンクがある
- 見出しが論理的な順序になっている(h1、h2、h3)
- インタラクティブ要素に適切なロールがある
3.6 レスポンシブデザインのレビュー
Tailwind とレスポンシブデザインのパターンを確認します:
// ❌ 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>
レスポンシブデザインチェックリスト:
- モバイルファーストのアプローチ(モバイル向けの基本スタイル)
- ブレークポイントが適切である(sm、md、lg、xl、2xl)
- 柔軟なレイアウト(flex、grid)
- レスポンシブ画像(w-full h-auto、object-fit)
- レスポンシブなタイポグラフィ(画面サイズに応じたテキストサイズ)
- タッチ操作しやすいヒット領域(最小 44x44px)
- モバイルで横スクロールが発生しない
- 複数の画面サイズでテスト済み
3.7 コード品質のレビュー
可読性と保守性を評価します:
// ❌ 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>
);
}
コード品質チェックリスト:
- 明確で説明的な名前を使用する(gn ではなく getUserName)
- マジックナンバーやマジック文字列を使用しない(定数を使用する)
- 深いネストではなく早期リターンを使用する
- 関数やコンポーネントを小さく保つ(100 行未満)
- 単一責任の原則
- DRY(同じことを繰り返さない)
- コメントアウトされたコードがない
- 本番コードに console.log がない
- コードスタイルが一貫している
- エラーを適切に処理する
3.8 React Hooks のベストプラクティス
// ❌ 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>;
}
ステップ 4:構造化されたフィードバックを提供する
レビュー結果をわかりやすく整理します:
## 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
優先度:高 ⚠️
[同じ形式]
優先度:中 ⚡
[同じ形式]
優先度:低 💡
[同じ形式]
良い点 ✅
- [良い点を強調する]
- [優れたプラクティスを挙げる]
推奨事項
- [実行可能な改善案]
- [実行可能な改善案]
総合スコア
- セキュリティ:8/10
- パフォーマンス:7/10
- コード品質:9/10
- アクセシビリティ:6/10
- 型安全性:8/10
総合: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!