ワンクリックで
react
React and TypeScript development with Tailwind, shadcn/ui, and modern patterns
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
React and TypeScript development with Tailwind, shadcn/ui, and modern patterns
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Comprehensive iOS development guidance covering SwiftUI, Swift Concurrency, Core Data, and Swift Testing
SQLiteData persistence library (Point-Free) - a fast SwiftData alternative using SQLite/GRDB. Use when working with SQLiteData, GRDB, type-safe SQL queries, @Table macro, @FetchAll/@FetchOne property wrappers, or migrating from SwiftData to SQLite-based persistence.
Technical blog writing with engaging hooks, progressive complexity, and code-first explanations
Comprehensive code review for quality, security, performance, and best practices across any language
Designs comprehensive implementation plans for new features with architecture considerations
Creates distinctive, production-grade frontend interfaces with high design quality, avoiding generic AI aesthetics
| name | react |
| description | React and TypeScript development with Tailwind, shadcn/ui, and modern patterns |
You are a React and TypeScript expert. Apply these guidelines when working on React projects.
This skill assumes:
src/
├── app/ # Next.js app router pages
├── components/
│ ├── ui/ # shadcn/ui components
│ └── features/ # Feature-specific components
├── hooks/ # Custom hooks
├── lib/ # Utilities (cn, api clients)
├── types/ # Shared TypeScript types
└── services/ # API and data fetching
// Prefer interfaces for extendable objects
interface User {
id: string;
name: string;
email: string;
}
// Use type for unions and computed types
type Status = 'idle' | 'loading' | 'success' | 'error';
type UserWithRole = User & { role: Role };
// Props interfaces above components
interface ButtonProps {
variant?: 'default' | 'destructive' | 'outline';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
onClick?: () => void;
children: React.ReactNode;
}
// Reusable hooks with generics
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
return [value, setValue] as const;
}
// Constrained generics
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSubmit(formData);
};
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
onClick?.();
};
interface CardProps {
title: string;
description?: string;
children: React.ReactNode;
className?: string;
}
export function Card({
title,
description,
children,
className
}: CardProps) {
return (
<div className={cn(
"rounded-lg border bg-card p-6 shadow-sm",
className
)}>
<h3 className="text-lg font-semibold">{title}</h3>
{description && (
<p className="mt-1 text-sm text-muted-foreground">
{description}
</p>
)}
<div className="mt-4">{children}</div>
</div>
);
}
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function CreateItemDialog() {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Create Item</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Item</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit}>
<Input placeholder="Item name" />
<Button type="submit" className="mt-4">
Create
</Button>
</form>
</DialogContent>
</Dialog>
);
}
// lib/utils.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
<div
className={cn(
"rounded-lg border p-4 transition-colors",
isActive && "border-primary bg-primary/5",
isDisabled && "cursor-not-allowed opacity-50",
className
)}
>
{children}
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{items.map((item) => (
<ItemCard key={item.id} item={item} />
))}
</div>
// Centering
<div className="flex min-h-screen items-center justify-center">
// Card container
<div className="rounded-lg border bg-card p-6 shadow-sm">
// Flex with gap
<div className="flex items-center gap-2">
// Truncated text
<p className="truncate text-sm text-muted-foreground">
// Hover states
<button className="hover:bg-accent hover:text-accent-foreground">
// useState with explicit type when needed
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<Item[]>([]);
// useReducer for complex state
type Action =
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_DATA'; payload: Data }
| { type: 'SET_ERROR'; payload: Error };
const [state, dispatch] = useReducer(reducer, initialState);
// With cleanup
useEffect(() => {
const controller = new AbortController();
fetchData(controller.signal)
.then(setData)
.catch((error) => {
if (!controller.signal.aborted) {
setError(error);
}
});
return () => controller.abort();
}, [dependency]);
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue((v) => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return { value, toggle, setTrue, setFalse };
}
// Server Component - no 'use client' directive
async function UserList() {
const users = await fetchUsers();
return (
<ul className="space-y-2">
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
'use client';
function useUsers() {
const [users, setUsers] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const controller = new AbortController();
async function load() {
try {
const data = await fetchUsers(controller.signal);
setUsers(data);
} catch (e) {
if (!controller.signal.aborted) {
setError(e as Error);
}
} finally {
setIsLoading(false);
}
}
load();
return () => controller.abort();
}, []);
return { users, isLoading, error };
}
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const schema = z.object({
email: z.string().email("Invalid email"),
password: z.string().min(8, "Password must be at least 8 characters"),
});
type FormData = z.infer<typeof schema>;
export function LoginForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormData>({
resolver: zodResolver(schema),
});
const onSubmit = async (data: FormData) => {
await login(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<Input {...register("email")} placeholder="Email" />
{errors.email && (
<p className="mt-1 text-sm text-destructive">
{errors.email.message}
</p>
)}
</div>
<div>
<Input
{...register("password")}
type="password"
placeholder="Password"
/>
{errors.password && (
<p className="mt-1 text-sm text-destructive">
{errors.password.message}
</p>
)}
</div>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Signing in..." : "Sign In"}
</Button>
</form>
);
}
kebab-case.tsx or PascalCase.tsxuse-hook-name.tskebab-case.tstypes.ts or type-name.ts// Components: PascalCase
function UserProfile() {}
// Hooks: camelCase with 'use' prefix
function useAuth() {}
// Constants: SCREAMING_SNAKE_CASE
const API_BASE_URL = '/api';
const MAX_RETRY_COUNT = 3;
// Event handlers: handle + Event
const handleClick = () => {};
const handleSubmit = () => {};
const handleInputChange = () => {};
// Boolean variables: is/has/can/should prefix
const isLoading = true;
const hasError = false;
const canSubmit = true;
'use client';
interface ErrorBoundaryProps {
children: React.ReactNode;
fallback: React.ReactNode;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, { hasError: boolean }> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
class ApiError extends Error {
constructor(
message: string,
public statusCode: number,
public code?: string
) {
super(message);
this.name = 'ApiError';
}
}
async function fetchWithError<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new ApiError(
'Request failed',
response.status,
response.statusText
);
}
return response.json();
}
// Memoize expensive computations
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// Memoize callbacks passed to children
const handleDelete = useCallback((id: string) => {
setItems((prev) => prev.filter((item) => item.id !== id));
}, []);
// Memoize components that render often
const MemoizedRow = memo(function Row({ item }: { item: Item }) {
return <div>{item.name}</div>;
});
// Lazy load heavy components
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<Suspense fallback={<Skeleton className="h-64 w-full" />}>
<HeavyChart data={data} />
</Suspense>
);
}
# Create new Next.js project
pnpm create next-app@latest my-app --typescript --tailwind --eslint
# Add shadcn/ui
pnpm dlx shadcn@latest init
# Add shadcn components
pnpm dlx shadcn@latest add button input dialog
# Add dependencies
pnpm add zod react-hook-form @hookform/resolvers
# Development
pnpm dev
# Build
pnpm build
as casting