React and TypeScript development with Tailwind, shadcn/ui, and modern patterns
/plugin marketplace add onmyway133/claude-code-plugins/plugin install onmyway133-super-plugins-super@onmyway133/claude-code-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
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 castingThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.