ワンクリックで
frontend
Frontend development rules and patterns for Next.js/React projects
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Frontend development rules and patterns for Next.js/React projects
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | frontend |
| description | Frontend development rules and patterns for Next.js/React projects |
any unless absolutely necessarytype instead of interface for simple type definitionstype import for interfaces exported from modulessrc/types folder, organized by domainPick, Omit, Partial) for reusable patternsDocumentGetting.tsx → document-getting.tsx, useDocument → use-document.ts)See code-style.mdc for full details.
components/components/ui/components/[feature]/See react.mdc for full details.
rem units for spacing, sizing, and typography — never pxSee ui.mdc for full details.
See layout.mdc for full details (form placement, data-fetching states, grid rules).
Every data-fetching component must handle:
Loading — Show skeleton placeholder matching the content layout:
if (!data) return <Skeleton />;
Empty — Show empty state message:
<EmptyState isEmpty={data.length === 0}>
<EmptyStateContent>
<EmptyStateTitle>title</EmptyStateTitle>
<EmptyStateDescription>description</EmptyStateDescription>
</EmptyStateContent>
<EmptyStateConceal>
<Content data={data} />
</EmptyStateConceal>
</EmptyState>
Success — Render the data inside <EmptyStateConceal>
Prefer shadcn Skeleton component for loading.
See form.mdc and form.dialog.mdc for full form and dialog patterns.
initialFormData (optional), isLoading (optional), onSubmit (required), explicit defaultState, ref for form.reset()form.formState.isSubmitting is truesonner:
toast.success("Record created successfully");
toast.error("Error creating a record", { description: 'Error Message' });
[FormName]Dialog (e.g., CaseFormDialog.tsx in components/[feature]/)
open/onOpenChange statechildren: React.ReactNode (trigger button element)[FormName]Integrated inside DialogContent[FormName]Integrated (e.g., CaseFormIntegrated.tsx in components/[feature]/)
onClose: () => void[FormName]Form with submission handler[FormName]Form (e.g., CaseForm.tsx in components/forms/)
initialFormData?, submitHandler, ref?See table.mdc for full TanStack Table rules via AppDataTable.
See tanstack-router.mdc for routing conventions.
See next-js.mdc for Next.js-specific rules.
hooks/See friendly-errors.md for user-friendly error patterns.
const formatPrice = (price: number) =>
new Intl.NumberFormat("en-NG", {
style: "currency",
currency: "NGN",
maximumFractionDigits: 0,
}).format(price);
Returns: "₦1,500"
// Query
const data = useQuery(api.myFunction, { arg });
// Mutation
const mutate = useMutation(api.myMutation);
mutate({ arg });
See form.mdc for full form generation rules.
See form.dialog.mdc for the full form-in-dialog pattern.
Tabs, TabsList, TabsTrigger, TabsContentInstead of repeated find() calls in JSX:
// Bad - repeated find in JSX
<Form initialData={currentPlanId ? plans.find(p => p.id === currentPlanId) : undefined} />
// Good - extract to variable
const currentPlan = plans.find(p => p.id === currentPlanId);
<Form initialData={currentPlan} />
const computed = React.useMemo(() => expensiveOperation(data), [data]);
Return early to reduce nesting:
if (!data) return <Skeleton />;
return ( /* main content */ );
Avoid creating separate state for derived values:
// Bad - duplicate state
const [filtered, setFiltered] = useState(data);
useEffect(() => setFiltered(data.filter(...)), [data]);
// Good - derive in render
const filtered = data.filter(...);
// Bad
<Component a={obj.a} b={obj.b} c={obj.c} d={obj.d} />
// Good
const { a, b, c, d } = obj;
<Component a={a} b={b} c={c} d={d} />
// Or as spread
<Component {...pick(obj, 'a', 'b', 'c', 'd')} />
Forms should use local isSubmitting state passed to isLoading prop:
const [isSubmitting, setIsSubmitting] = useState(false);
<Form
isLoading={isSubmitting}
onSubmit={async (values) => {
setIsSubmitting(true);
try {
await mutation(values);
} finally {
setIsSubmitting(false);
}
}}
/>
// Form button shows "Saving..." when isLoading=true
Inline functions cause child components to rerender on every parent render:
// Bad - creates new function each render
<Button onClick={() => handleClick(id)} />
// Good - use stable handler, pass id as argument
<Button onClick={handleClick} payload={id} />
Prefer map with direct access over multiple/nested ternary operations for JavaScript conditionals.
// Bad - nested ternaries
const value = var === "a" ? 1 : var === "b" ? 2 : var === "c" ? 3 : "default";
// Good - map lookup
const valueMap = {
"a": 1,
"b": 2,
"c": 3
};
const value = valueMap[var] || "default";
// Show/Hide with && (for no else case)
{isOpen && <Content />}
// Ternary for else case
{isOpen ? <OpenState /> : <ClosedState />}
Group related components for cleaner exports:
// Instead of
export { Button } from "./button";
export { ButtonGroup } from "./button-group";
// Use compound component
export const Button = Object.assign(ButtonRoot, { Group: ButtonGroup });
// Usage: <Button.Group>
bun format, pnpm format