--- name: web-artifacts-builder description: Modern web development patterns using React + Tailwind CSS + shadcn/ui for building production-quality, accessible, and performant web applications version: 1.0.0 --- ## Overview This skill provides comprehensive patterns and best practices for building modern web applications using the React + Tailwind CSS + shadcn/ui stack. It emphasizes component-driven development, type safety with TypeScript, accessibility, and performance optimization. ## Stack Overview **Core Technologies**: - **React 18+**: Component-based UI with hooks and concurrent features - **TypeScript**: Type-safe development with excellent IDE support - **Tailwind CSS**: Utility-first CSS framework for rapid styling - **shadcn/ui**: High-quality, accessible React components built on Radix UI - **Vite**: Fast build tool with HMR and optimized production builds **Why This Stack**: - Type safety reduces bugs and improves maintainability - Tailwind enables rapid UI development without context switching - shadcn/ui provides accessible, customizable components out of the box - Vite offers excellent developer experience and fast builds - Modern ecosystem with active community support **Design Considerations**: Based on research from ["Improving frontend design through Skills"](https://claude.com/blog/improving-frontend-design-through-skills): - **Avoid Distributional Defaults**: Don't use Inter/Roboto/Open Sans, purple gradients, or plain backgrounds - **Distinctive Typography**: Use high-contrast font pairings with extreme weight variations (100-200 or 800-900) - **Intentional Colors**: Move beyond generic color schemes with thoughtful palettes - **High-Impact Motion**: One well-orchestrated page load beats a dozen random animations - **Motion Library**: Use Framer Motion for complex animation choreography in React - See `frontend-aesthetics` skill for comprehensive design guidance ## Project Structure ### Recommended Directory Layout ``` project-root/ ├── src/ │ ├── components/ │ │ ├── ui/ # shadcn/ui components │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── dialog.tsx │ │ │ └── ... │ │ ├── features/ # Feature-specific components │ │ │ ├── auth/ │ │ │ ├── dashboard/ │ │ │ └── ... │ │ └── layout/ # Layout components │ │ ├── Header.tsx │ │ ├── Sidebar.tsx │ │ └── Footer.tsx │ ├── lib/ │ │ ├── utils.ts # Utility functions (cn, etc.) │ │ ├── api.ts # API client │ │ └── hooks/ # Custom React hooks │ ├── pages/ # Page components (if using routing) │ ├── styles/ │ │ └── globals.css # Global styles and Tailwind imports │ ├── types/ # TypeScript type definitions │ ├── App.tsx # Main app component │ └── main.tsx # Entry point ├── public/ # Static assets ├── index.html ├── package.json ├── tsconfig.json ├── tailwind.config.js ├── vite.config.ts └── components.json # shadcn/ui configuration ``` ## Component Patterns ### 1. Button Component (shadcn/ui style) ```typescript // src/components/ui/button.tsx import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2", sm: "h-8 rounded-md px-3 text-xs", lg: "h-10 rounded-md px-8", icon: "h-9 w-9", }, }, defaultVariants: { variant: "default", size: "default", }, } ) export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button" return ( ) } ) Button.displayName = "Button" export { Button, buttonVariants } ``` **Usage**: ```typescript import { Button } from "@/components/ui/button" // Different variants ``` ### 2. Card Component Pattern ```typescript // src/components/ui/card.tsx import * as React from "react" import { cn } from "@/lib/utils" const Card = React.forwardRef>( ({ className, ...props }, ref) => (
) ) Card.displayName = "Card" const CardHeader = React.forwardRef>( ({ className, ...props }, ref) => (
) ) CardHeader.displayName = "CardHeader" const CardTitle = React.forwardRef>( ({ className, ...props }, ref) => (

) ) CardTitle.displayName = "CardTitle" const CardDescription = React.forwardRef>( ({ className, ...props }, ref) => (

) ) CardDescription.displayName = "CardDescription" const CardContent = React.forwardRef>( ({ className, ...props }, ref) => (

) ) CardContent.displayName = "CardContent" const CardFooter = React.forwardRef>( ({ className, ...props }, ref) => (
) ) CardFooter.displayName = "CardFooter" export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } ``` **Usage**: ```typescript import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card" import { Button } from "@/components/ui/button" Card Title Card description goes here

Card content

``` ### 3. Dialog/Modal Component ```typescript // src/components/ui/dialog.tsx (simplified shadcn/ui pattern) import * as React from "react" import * as DialogPrimitive from "@radix-ui/react-dialog" import { X } from "lucide-react" import { cn } from "@/lib/utils" const Dialog = DialogPrimitive.Root const DialogTrigger = DialogPrimitive.Trigger const DialogPortal = DialogPrimitive.Portal const DialogClose = DialogPrimitive.Close const DialogOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) DialogOverlay.displayName = DialogPrimitive.Overlay.displayName const DialogContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( {children} Close )) DialogContent.displayName = DialogPrimitive.Content.displayName export { Dialog, DialogPortal, DialogOverlay, DialogTrigger, DialogClose, DialogContent } ``` ## Tailwind CSS Configuration ### Enhanced tailwind.config.js ```javascript /** @type {import('tailwindcss').Config} */ export default { darkMode: ["class"], content: [ './pages/**/*.{ts,tsx}', './components/**/*.{ts,tsx}', './app/**/*.{ts,tsx}', './src/**/*.{ts,tsx}', ], theme: { container: { center: true, padding: "2rem", screens: { "2xl": "1400px", }, }, extend: { colors: { border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))", }, secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))", }, destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))", }, muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))", }, accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))", }, popover: { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))", }, card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))", }, }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, keyframes: { "accordion-down": { from: { height: 0 }, to: { height: "var(--radix-accordion-content-height)" }, }, "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: 0 }, }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", }, }, }, plugins: [require("tailwindcss-animate")], } ``` ### CSS Variables (globals.css) ```css @tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; --primary: 221.2 83.2% 53.3%; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: 221.2 83.2% 53.3%; --radius: 0.5rem; } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; --primary: 217.2 91.2% 59.8%; --primary-foreground: 222.2 47.4% 11.2%; --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 224.3 76.3% 48%; } } @layer base { * { @apply border-border; } body { @apply bg-background text-foreground; } } ``` ## Utility Functions ### cn() - Class Name Utility ```typescript // src/lib/utils.ts import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` **Usage**: Merge Tailwind classes without conflicts ```typescript cn("px-2 py-1", "px-3") // Result: "py-1 px-3" (px-3 overrides px-2) ``` ## TypeScript Best Practices ### Component Props Typing ```typescript // Extend HTML attributes interface CustomButtonProps extends React.ButtonHTMLAttributes { variant?: 'primary' | 'secondary' | 'outline' size?: 'sm' | 'md' | 'lg' loading?: boolean } // Or use type type CustomButtonProps = { variant?: 'primary' | 'secondary' | 'outline' size?: 'sm' | 'md' | 'lg' loading?: boolean } & React.ButtonHTMLAttributes ``` ### API Response Typing ```typescript // Define API response types interface User { id: string name: string email: string role: 'admin' | 'user' } interface ApiResponse { data: T message: string status: 'success' | 'error' } // Usage const fetchUser = async (id: string): Promise> => { const response = await fetch(`/api/users/${id}`) return response.json() } ``` ## Accessibility Patterns ### Keyboard Navigation ```typescript // Focus management const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Escape') { onClose() } if (e.key === 'Enter' || e.key === ' ') { onSelect() e.preventDefault() } } // Trap focus in modal import { useFocusTrap } from '@/lib/hooks/use-focus-trap' function Modal({ children }: { children: React.ReactNode }) { const modalRef = useFocusTrap() return (
{children}
) } ``` ### ARIA Labels ```typescript // Screen reader support // Form accessibility
{hasError && }
``` ## Performance Optimization ### Code Splitting ```typescript // Lazy load routes import { lazy, Suspense } from 'react' const Dashboard = lazy(() => import('./pages/Dashboard')) const Settings = lazy(() => import('./pages/Settings')) function App() { return ( }> } /> } /> ) } ``` ### Memoization ```typescript import { memo, useMemo, useCallback } from 'react' // Memo component const ExpensiveComponent = memo(({ data }: { data: Data[] }) => { return
{/* Render data */}
}) // Memo computation function Component({ items }: { items: Item[] }) { const sortedItems = useMemo( () => items.sort((a, b) => a.name.localeCompare(b.name)), [items] ) const handleClick = useCallback(() => { console.log('Clicked') }, []) return } ``` ## Animation with Framer Motion ### Installation ```bash npm install framer-motion ``` ### Core Principles **High-Impact Moments Over Random Motion**: - One well-orchestrated page load with staggered reveals > dozen random micro-animations - Focus on: page load, route transitions, major state changes - Use CSS for simple transitions, Framer Motion for complex choreography - Always respect `prefers-reduced-motion` ### Page Transitions ```typescript // app/layout.tsx or page wrapper import { motion, AnimatePresence } from 'framer-motion' export default function PageTransition({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` ### Staggered List Animation ```typescript import { motion } from 'framer-motion' const container = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.1 // Delay between each child animation } } } const item = { hidden: { opacity: 0, y: 20 }, show: { opacity: 1, y: 0 } } export function StaggeredList({ items }: { items: string[] }) { return ( {items.map((item, i) => ( {item} ))} ) } ``` ### Card Hover Effects ```typescript import { motion } from 'framer-motion' export function AnimatedCard({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` ### Layout Animations (Shared Layout) ```typescript import { motion, LayoutGroup } from 'framer-motion' export function TabsWithAnimation({ tabs }: { tabs: Tab[] }) { const [activeTab, setActiveTab] = useState(0) return (
{tabs.map((tab, i) => ( setActiveTab(i)} className="relative px-4 py-2 rounded-md" whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} > {tab.label} {activeTab === i && ( )} ))}
) } ``` ### Scroll-Based Animations ```typescript import { motion, useScroll, useTransform } from 'framer-motion' import { useRef } from 'react' export function ParallaxSection() { const ref = useRef(null) const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end start"] }) const y = useTransform(scrollYProgress, [0, 1], [0, -100]) const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [0, 1, 0]) return (

Parallax Content

) } ``` ### Modal / Dialog Animations ```typescript import { motion, AnimatePresence } from 'framer-motion' export function AnimatedDialog({ open, onClose, children }: { open: boolean onClose: () => void children: React.ReactNode }) { return ( {open && ( <> {/* Backdrop */} {/* Dialog content */} {children} )} ) } ``` ### Gesture Animations ```typescript import { motion, useDragControls } from 'framer-motion' export function DraggableCard() { const controls = useDragControls() return ( ) } // Swipe to dismiss export function SwipeableBanner({ onDismiss }: { onDismiss: () => void }) { return ( { if (Math.abs(offset.x) > 100 || Math.abs(velocity.x) > 500) { onDismiss() } }} className="p-4 bg-card border rounded-lg" > Swipe to dismiss ) } ``` ### Loading States with Animation ```typescript import { motion } from 'framer-motion' export function LoadingSpinner() { return ( ) } export function PulseLoader() { return (
{[0, 1, 2].map((i) => ( ))}
) } ``` ### Respecting Reduced Motion ```typescript import { motion, useReducedMotion } from 'framer-motion' export function AccessibleAnimation({ children }: { children: React.ReactNode }) { const shouldReduceMotion = useReducedMotion() return ( {children} ) } ``` ### Performance Best Practices **What to Animate (GPU-Accelerated)**: - `opacity` - `transform` (translate, scale, rotate) - `filter` (blur, brightness) **What to Avoid Animating**: - `width`, `height` (causes layout thrashing) - `top`, `left`, `margin`, `padding` (use `transform: translate` instead) - `color`, `background-color` (expensive, use sparingly) **Optimization Tips**: ```typescript // Use will-change for smoother animations // Lazy load motion components import { motion, LazyMotion, domAnimation } from "framer-motion" // Reduce animation complexity on low-end devices const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches ``` ### When to Use Framer Motion vs CSS **Use CSS Animations**: - Simple hover effects - Basic transitions - Static keyframe animations - Better performance for simple cases **Use Framer Motion**: - Complex orchestration (staggered lists, sequences) - Gesture-based interactions (drag, swipe) - Layout animations (morphing between states) - Scroll-based animations - Dynamic animations based on state - Need for easier control and declarative API ## When to Apply Use this skill when: - Building modern React applications with TypeScript - Implementing accessible, production-quality UI components - Using Tailwind CSS for styling - Integrating shadcn/ui component library - Setting up new projects with Vite + React - Creating reusable component libraries - Implementing design systems with consistent patterns - Building dashboards, admin panels, or web applications This stack provides excellent developer experience, type safety, accessibility, and performance for modern web development.