一键导入
typescript-patterns
Use when writing TypeScript or React — patterns for shadcn/ui conventions, Tailwind v4, hooks, and robust frontend applications.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when writing TypeScript or React — patterns for shadcn/ui conventions, Tailwind v4, hooks, and robust frontend applications.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when starting any creative work — creating features, components, or functionality, modifying behavior, or solving ambiguous problems — before design or implementation. Covers product discovery, competitive research, feature analysis, technical design, and capability spec writing. Applies to every project; routes to writing-plans (non-UI) or design-workflow (UI).
Use when finishing an edit to source files — runs format, typecheck, and console.log detection before moving to the next task.
Use when implementation of a bug fix or feature task is complete — dispatches the appropriate language and global reviewers and returns a binary APPROVE/BLOCK verdict.
Use when about to git commit — validates commit message format, lints staged files, and scans staged content for secrets and debug artifacts.
Use when reaching the design-workflow V2-4 hard gate — adversarially reviews design artifacts (token coverage, contract completeness, artifact consistency, accessibility docs, responsive coverage, DESIGN.md compliance) against docs/designs/<feature>/ before development handoff.
Use when a task involves UI — new pages, components, visual interactions, or design tokens — including work in docs/designs/, *.pen files, or with Pencil MCP tools. Skip for backend-only, config-only, or refactoring without visual impact. Routes to writing-plans on completion.
| name | typescript-patterns |
| description | Use when writing TypeScript or React — patterns for shadcn/ui conventions, Tailwind v4, hooks, and robust frontend applications. |
| origin | rune |
TypeScript, React, shadcn/ui, and Tailwind v4 patterns and best practices for building robust frontend applications.
Add parameter and return types to exported functions, shared utilities, and public class methods. Let TypeScript infer obvious local variable types.
// Good: Explicit types on public APIs
interface User {
firstName: string
lastName: string
}
export function formatUser(user: User): string {
return `${user.firstName} ${user.lastName}`
}
interface for object shapes that may be extended or implementedtype for unions, intersections, tuples, mapped types, and utility typesenuminterface User {
id: string
email: string
}
type UserRole = 'admin' | 'member'
type UserWithRole = User & {
role: UserRole
}
anyUse unknown for external or untrusted input, then narrow safely.
function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message
}
return 'Unexpected error'
}
Define component props with a named interface or type. Do not use React.FC.
interface User {
id: string
email: string
}
interface UserCardProps {
user: User
onSelect: (id: string) => void
}
function UserCard({ user, onSelect }: UserCardProps) {
return <button onClick={() => onSelect(user.id)}>{user.email}</button>
}
In .js and .jsx files, use JSDoc when types improve clarity and a TypeScript migration is not practical.
/**
* @param {{ firstName: string, lastName: string }} user
* @returns {string}
*/
export function formatUser(user) {
return `${user.firstName} ${user.lastName}`
}
Use spread operator for immutable updates. Never mutate existing objects.
interface User {
id: string
name: string
}
function updateUser(user: Readonly<User>, name: string): User {
return {
...user,
name
}
}
Use async/await with try-catch and narrow unknown errors safely.
interface User {
id: string
email: string
}
declare function riskyOperation(userId: string): Promise<User>
function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message
}
return 'Unexpected error'
}
const logger = {
error: (message: string, error: unknown) => {
// Replace with your production logger (e.g., pino or winston).
}
}
async function loadUser(userId: string): Promise<User> {
try {
const result = await riskyOperation(userId)
return result
} catch (error: unknown) {
logger.error('Operation failed', error)
throw new Error(getErrorMessage(error))
}
}
Use Zod for schema-based validation and infer types from the schema.
import { z } from 'zod'
const userSchema = z.object({
email: z.string().email(),
age: z.number().int().min(0).max(150)
})
type UserInput = z.infer<typeof userSchema>
const validated: UserInput = userSchema.parse(input)
NEVER use arbitrary values. Always use semantic Tailwind utilities that map to design tokens.
// BAD: Arbitrary values bypass the design system
<div className="bg-[#3b82f6] rounded-[6px] p-[24px] text-[14px]">
// GOOD: Semantic classes reference design tokens
<div className="bg-primary rounded-md p-6 text-sm">
Use Tailwind v4 @theme blocks in CSS, not tailwind.config.ts.
/* GOOD: Tailwind v4 @theme block */
@import "tailwindcss";
@theme {
--color-primary: oklch(0.6 0.2 260);
--color-destructive: oklch(0.6 0.2 25);
--radius-md: 0.375rem;
--font-sans: "Inter", sans-serif;
}
Always use cn() from @/lib/utils to merge classes. Never concatenate class strings manually.
import { cn } from "@/lib/utils"
// BAD: String concatenation or template literals
<div className={"card " + (active ? "active" : "")}>
<div className={`card ${active ? "active" : ""}`}>
// GOOD: cn() handles conflicts and conditionals
<div className={cn("rounded-md border", active && "ring-2 ring-primary")}>
Import shadcn/ui components from @/components/ui/. Do not recreate components that shadcn already provides.
import { Button } from "@/components/ui/button"
import { Card, CardHeader, CardContent } from "@/components/ui/card"
import { Dialog, DialogContent } from "@/components/ui/dialog"
Use Lucide React icons only. Do not use Material Icons or other icon libraries.
import { AlertTriangle, Check, X } from "lucide-react"
Extend shadcn/ui components for project-specific needs. Use CVA (class-variance-authority) for variants.
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva("inline-flex items-center justify-center", {
variants: {
intent: {
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground",
ghost: "hover:bg-accent hover:text-accent-foreground",
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base",
},
},
defaultVariants: {
intent: "primary",
size: "md",
},
})
Compose shadcn/ui primitives into domain-specific components.
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { AlertTriangle, Info } from "lucide-react"
const alertCardVariants = cva("", {
variants: {
severity: {
critical: "border-destructive",
warning: "border-yellow-500",
info: "border-primary",
},
},
defaultVariants: {
severity: "info",
},
})
interface AlertCardProps extends VariantProps<typeof alertCardVariants> {
title: string
description: string
className?: string
}
function AlertCard({ title, description, severity, className }: AlertCardProps) {
return (
<Card className={cn(alertCardVariants({ severity }), className)}>
<CardHeader className="flex flex-row items-center gap-2">
{severity === "critical" ? (
<AlertTriangle className="h-4 w-4 text-destructive" />
) : (
<Info className="h-4 w-4 text-primary" />
)}
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">{description}</p>
</CardContent>
</Card>
)
}
Wrap shadcn/ui components to add domain behavior while keeping the base component swappable.
import { Button } from "@/components/ui/button"
import { Loader2 } from "lucide-react"
interface ActionButtonProps {
loading?: boolean
children: React.ReactNode
onClick: () => void
}
function ActionButton({ loading, children, onClick }: ActionButtonProps) {
return (
<Button onClick={onClick} disabled={loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{children}
</Button>
)
}
Reference tokens through semantic Tailwind classes, never through hardcoded values. Tokens are defined in the CSS @theme block (from design-workflow token pipeline).
// Tokens flow: Pencil variables -> w3c.json -> Style Dictionary -> @theme -> Tailwind classes
// GOOD: Semantic classes that reference tokens
<div className="bg-card text-card-foreground rounded-lg p-6 shadow-md">
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
<p className="text-sm text-muted-foreground">{description}</p>
</div>
// BAD: Hardcoded values that bypass the design system
<div className="bg-white text-gray-900 rounded-lg p-6 shadow-md">
Use a consistent envelope for all API responses.
interface ApiResponse<T> {
success: boolean
data?: T
error?: string
meta?: {
total: number
page: number
limit: number
}
}
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay)
return () => clearTimeout(handler)
}, [value, delay])
return debouncedValue
}
Encapsulate data access behind a consistent interface.
interface Repository<T> {
findAll(filters?: Filters): Promise<T[]>
findById(id: string): Promise<T | null>
create(data: CreateDto): Promise<T>
update(id: string, data: UpdateDto): Promise<T>
delete(id: string): Promise<void>
}
Never hardcode secrets. Use environment variables with startup validation.
// BAD: Hardcoded secrets
const openAiCredential = "sk-abc123"
// GOOD: Environment variables with validation
const openAiApiKey = process.env.OPENAI_API_KEY
if (!openAiApiKey) {
throw new Error('OPENAI_API_KEY not configured')
}
Enforce Tailwind/shadcn constraints at lint time:
npm install --save-dev eslint-plugin-tailwindcss
// eslint.config.js
{
rules: {
"tailwindcss/no-custom-classname": "error", // blocks bg-[#hex], w-[375px], etc.
"tailwindcss/classnames-order": "warn", // consistent class ordering
}
}