diff --git a/skills/react-patterns/SKILL.md b/skills/react-patterns/SKILL.md
new file mode 100644
index 00000000..f1340c8c
--- /dev/null
+++ b/skills/react-patterns/SKILL.md
@@ -0,0 +1,341 @@
+---
+name: react-patterns
+description: React 18/19 patterns including hooks discipline, server/client component boundaries, Suspense + error boundaries, form actions, data fetching, state management decision trees, and accessibility-first composition. Use when writing or reviewing React components.
+origin: ECC
+---
+
+# React Patterns
+
+Idiomatic React 18/19 patterns for building robust, accessible, performant component trees.
+
+## When to Activate
+
+- Writing or modifying React function components, custom hooks, or component trees
+- Reviewing JSX/TSX files
+- Designing state shape or component composition
+- Migrating class components or older `forwardRef`/`useEffect`-heavy code
+- Choosing between local state, lifted state, context, and external stores
+- Working with Server Components / Client Components (Next.js App Router, RSC)
+- Implementing forms with React 19 actions or controlled inputs
+- Wiring data fetching with TanStack Query / SWR / RSC
+
+## Core Principles
+
+### 1. Render is a Pure Function of Props and State
+
+```tsx
+// Good: derive during render
+function Cart({ items }: { items: CartItem[] }) {
+ const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
+ return {formatMoney(total)};
+}
+
+// Bad: derived state stored separately
+function Cart({ items }: { items: CartItem[] }) {
+ const [total, setTotal] = useState(0);
+ useEffect(() => {
+ setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));
+ }, [items]);
+ return {formatMoney(total)};
+}
+```
+
+Derived state in `useEffect` adds a render cycle, can desync, and obscures the data flow.
+
+### 2. Side Effects Outside Render
+
+Effects, mutations, network calls, and subscriptions live in event handlers or `useEffect` — never in the render body.
+
+### 3. Composition Over Inheritance
+
+React has no inheritance model for components. Compose with `children`, render props, or component props.
+
+## Hooks Discipline
+
+See [rules/react/hooks.md](../../rules/react/hooks.md) for the full ruleset. Highlights:
+
+- Top-level only, never conditional
+- Cleanup every subscription, interval, listener
+- Functional updater (`setX(prev => prev + 1)`) when new state depends on old
+- Default position: do not memoize — add `useMemo`/`useCallback` only when a profiler or a dependency chain proves it matters
+- Extract a custom hook only when the same hook sequence appears in 2+ components
+
+## State Location Decision Tree
+
+```
+Used by one component?
+ -> useState inside it
+
+Used by parent + a few descendants?
+ -> lift to nearest common ancestor
+
+Used across distant branches AND low-frequency reads (theme, auth, locale)?
+ -> React Context
+
+High-frequency updates shared across the tree?
+ -> external store (Zustand, Jotai, Redux Toolkit)
+
+Derived from a server?
+ -> server-state library (TanStack Query, SWR, RSC fetch)
+```
+
+Most pages do not need context or a global store. Resist abstraction until duplicated lifting becomes painful.
+
+## Server / Client Components (RSC)
+
+```tsx
+// Server Component - default, async, never ships JS for itself
+export default async function ProductPage({ params }: { params: { id: string } }) {
+ const product = await db.product.findUnique({ where: { id: params.id } });
+ if (!product) notFound();
+ return ;
+}
+
+// Client Component - opt in with "use client"
+"use client";
+export function AddToCartButton({ productId }: { productId: string }) {
+ const [pending, startTransition] = useTransition();
+ return (
+
+ );
+}
+```
+
+Boundaries:
+
+- Server -> Client: pass serializable props or `children`
+- Client -> Server: invoke Server Actions via `
+ );
+}
+```
+
+### Controlled inputs
+
+Use controlled when the value drives other UI, formats on every keystroke, or implements real-time validation.
+
+### Complex forms
+
+For multi-step forms, dynamic field arrays, or cross-field validation: use a library (React Hook Form, TanStack Form). Roll-your-own state management for forms past trivial complexity is a maintenance trap.
+
+## Data Fetching Decision Matrix
+
+| Need | Tool |
+|---|---|
+| Per-request data in Next.js App Router | RSC `await fetch()` |
+| Client-side cache + mutations + invalidation | TanStack Query |
+| Lightweight client cache + revalidation | SWR |
+| Real-time subscriptions | Server-Sent Events, WebSockets, or the lib's subscription API |
+| One-off fire-and-forget | `fetch()` in an event handler |
+
+Avoid `useEffect` + `fetch` for application data — race conditions, no cache, no retry, no Suspense integration.
+
+## Composition Recipes
+
+### Slot via `children`
+
+```tsx
+
+
+ {content}
+
+```
+
+### Named slots
+
+```tsx
+} sidebar={}>
+
+
+```
+
+### Compound components (shared state via Context)
+
+```tsx
+
+
+ Profile
+ Settings
+
+
+
+
+```
+
+### Render prop / function-as-child
+
+Useful when the parent needs to pass parameters to the rendered output:
+
+```tsx
+
+ {({ data, isLoading }) => isLoading ? : }
+
+```
+
+Modern alternative: a hook (`useData(id)`) returning the same shape — usually cleaner.
+
+## Performance
+
+### When `React.memo` Actually Helps
+
+Wrap a component in `React.memo` only when:
+
+1. It re-renders frequently
+2. Its props are usually the same between renders
+3. Its render is measurably expensive
+
+`React.memo` adds an equality check on every render. If props differ on most renders, the check is pure overhead.
+
+### Avoiding Render Cascades
+
+- Lift state down rather than up where possible
+- Split context: one context per concern, so a change to `themeContext` does not re-render auth consumers
+- Use `useSyncExternalStore` for external state libraries — required for safe concurrent rendering
+
+### Lists
+
+- Provide stable `key` props (database id, not array index)
+- Virtualize long lists with `@tanstack/react-virtual` or `react-window` once visible item count exceeds ~50 with non-trivial rows
+
+## Accessibility-First Composition
+
+- Always render semantic HTML (`