Web・iOS・Androidの画面を、読み上げやキーボード操作に対応させ、ラベル、配色、操作対象の大きさなどをWCAG 2.2に沿って設計・点検するスキル。
- アイコンボタンの説明を付けたいとき
- キーボード操作とモーダルの点検
- コントラストや操作対象の大きさの確認
React 18/19の画面部品を実装・レビューする際に、状態管理やデータ取得、フォーム、読み込み・エラー表示、アクセシビリティの設計を支えるスキル。
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.
React 18/19のコンポーネント、つまり画面を構成する部品の実装とレビューを支えます。フックの使い方、状態を保持する場所、データ取得方法、部品の組み合わせ方を整理できます。サーバー側とブラウザー側で動く部品の境界や、Suspenseによる読み込み表示、エラー表示の設計も扱います。
状態をどこで管理するか迷うとき、フォームや検索画面を作るとき、古い実装を見直すときに向いています。処理結果を別の状態として重複保存する書き方や、不要な再描画を点検する手がかりもあります。キーボード操作、入力欄のラベル、フォーカス管理など、操作しやすさの確認にも使えます。
対象はReact 18/19です。TanStack QueryやSWRなどは用途別の選択肢として紹介されています。Next.js固有の機能やルーター固有の規約は各ドキュメントを参照する方針で、React Native固有の設計は対象外です。
この紹介文は、公開されている SKILL.md をもとに AI(Claude Haiku)が作成しました。正確な仕様は下の原文を確認してください。
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Idiomatic React 18/19 patterns for building robust, accessible, performant component trees.
forwardRef/useEffect-heavy code// Good: derive during render
function Cart({ items }: { items: CartItem[] }) {
const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
return <span>{formatMoney(total)}</span>;
}
// 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 <span>{formatMoney(total)}</span>;
}
Derived state in useEffect adds a render cycle, can desync, and obscures the data flow.
Effects, mutations, network calls, and subscriptions live in event handlers or useEffect — never in the render body.
React has no inheritance model for components. Compose with children, render props, or component props.
See rules/react/hooks.md for the full ruleset. Highlights:
setX(prev => prev + 1)) when new state depends on olduseMemo/useCallback only when a profiler or a dependency chain proves it mattersUsed 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 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 <ProductView product={product} />;
}
// Client Component - opt in with "use client"
"use client";
export function AddToCartButton({ productId }: { productId: string }) {
const [pending, startTransition] = useTransition();
return (
<button
disabled={pending}
onClick={() => startTransition(() => addToCart(productId))}
>
{pending ? "Adding..." : "Add to cart"}
</button>
);
}
Boundaries:
children<form action={...}> or imperatively from event handlersimport a Server Component from a Client Component file — compose them via children instead<ErrorBoundary fallback={<ErrorView />}>
<Suspense fallback={<UserSkeleton />}>
<UserDetail id={id} />
</Suspense>
</ErrorBoundary>
react-error-boundary for a hook-friendly wrapper"use client";
import { useActionState } from "react";
const initial = { error: null as string | null };
async function updateUserAction(_prev: typeof initial, formData: FormData) {
"use server";
const parsed = UserSchema.safeParse(Object.fromEntries(formData));
if (!parsed.success) return { error: "Invalid input" };
await db.user.update({ where: { id: parsed.data.id }, data: parsed.data });
return { error: null };
}
export function UserForm() {
const [state, formAction, pending] = useActionState(updateUserAction, initial);
return (
<form action={formAction}>
<input name="name" required />
<button type="submit" disabled={pending}>Save</button>
{state.error && <p role="alert">{state.error}</p>}
</form>
);
}
Use controlled when the value drives other UI, formats on every keystroke, or implements real-time validation.
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.
| 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.
children<Layout>
<Header />
<Main>{content}</Main>
</Layout>
<Page header={<Nav />} sidebar={<Filters />}>
<Results />
</Page>
<Tabs defaultValue="profile">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Panel value="profile"><Profile /></Tabs.Panel>
<Tabs.Panel value="settings"><Settings /></Tabs.Panel>
</Tabs>
Useful when the parent needs to pass parameters to the rendered output:
<DataLoader id={id}>
{({ data, isLoading }) => isLoading ? <Spinner /> : <UserCard user={data} />}
</DataLoader>
Modern alternative: a hook (useData(id)) returning the same shape — usually cleaner.
React.memo Actually HelpsWrap a component in React.memo only when:
React.memo adds an equality check on every render. If props differ on most renders, the check is pure overhead.
themeContext does not re-render auth consumersuseSyncExternalStore for external state libraries — required for safe concurrent renderingkey props (database id, not array index)@tanstack/react-virtual or react-window once visible item count exceeds ~50 with non-trivial rows<button>, <a>, <nav>, <main>) before reaching for role attributes<label htmlFor> or aria-label if visually labeled by an iconaxe in component tests (see skills/react-testing)This skill is router-agnostic. The patterns above work with React Router, TanStack Router, Next.js App Router, Remix Router. Router-specific patterns (loaders, actions, nested layouts) follow the router's documentation — those are framework concerns layered on top of React core.
react-native-patterns skill (not present yet)react-reviewer for code review, react-build-resolver for build/bundler errors/react-review, /react-build, /react-testfunction useDebounce<T>(value: T, delay = 300): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}
function SearchBox() {
const [query, setQuery] = useState("");
const debounced = useDebounce(query, 300);
const { data } = useQuery({
queryKey: ["search", debounced],
queryFn: () => searchApi(debounced),
enabled: debounced.length > 0,
});
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<Results items={data ?? []} />
</>
);
}
useOptimistic"use client";
import { useOptimistic } from "react";
export function MessageList({ messages }: { messages: Message[] }) {
const [optimistic, addOptimistic] = useOptimistic(
messages,
(state, newMessage: Message) => [...state, newMessage],
);
async function send(formData: FormData) {
const text = String(formData.get("text"));
addOptimistic({ id: "pending", text, sender: "me" });
await saveMessage(text);
}
return (
<>
<ul>{optimistic.map((m) => <li key={m.id}>{m.text}</li>)}</ul>
<form action={send}>
<input name="text" />
<button type="submit">Send</button>
</form>
</>
);
}
// Two contexts: one rarely changes, one frequently
const ThemeContext = createContext<Theme>("light");
const NotificationsContext = createContext<Notification[]>([]);
// A component that only consumes ThemeContext does NOT re-render when notifications change
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Web・iOS・Androidの画面を、読み上げやキーボード操作に対応させ、ラベル、配色、操作対象の大きさなどをWCAG 2.2に沿って設計・点検するスキル。
AIエージェントの不調を、指示・記憶・ツール実行・画面表示など12の層から調べるスキル。コードやログを根拠に原因を整理し、重要度順の指摘と修正案をまとめます。
実際の開発課題で複数のコーディングエージェントを比較するスキル。成功率、取得可能なAPI費用、所要時間、繰り返し実行の安定性を測り、選定や更新後の評価に使えます。
AIエージェントが使うツールの種類や入出力、エラーからの復帰手順を設計・見直します。文脈の情報量も整理し、作業完了率や再試行回数で改善を評価します。
AIエージェントの失敗や同じ操作の繰り返しを記録し、原因の切り分け、小さな復旧操作、結果の報告まで進める手順を示して、根拠のある再試行につなげるスキル。
AIエージェントが失敗や同じ操作を繰り返す原因を、エラーと実行状況から整理します。小さな復旧操作を試し、結果と根拠を引き継げる報告にまとめるスキルです。