Frontend Master Skill
Unified decision framework for modern frontend development.
Stack: Next.js 14+ (App Router) · React 18+ · Tailwind CSS · TypeScript · Framer Motion
Quick Decision Matrix
WHAT DO YOU NEED? │ ├─► UI Components │ ├─ Basic (buttons, forms, dialogs) → shadcn/ui │ ├─ SaaS polish (tickers, marquees) → Magic UI [skill: frontend-magic-ui] │ └─ Dramatic effects (spotlight, 3D) → Aceternity [skill: frontend-aceternity] │ ├─► Animations │ ├─ Just plays/loops (loaders, feedback) → Lottie [skill: frontend-lottie] │ └─ Reacts to input (hover, data) → Rive [skill: frontend-rive] │ ├─► Assets │ ├─ Icons → Iconify/Lucide [skill: frontend-iconify] │ ├─ Avatars → DiceBear (FREE) [skill: frontend-image-generation] │ ├─ Photos → Unsplash (FREE) [skill: frontend-image-generation] │ └─ Illustrations → unDraw (FREE) [skill: frontend-image-generation] │ ├─► Theming │ ├─ Colors/palette → Color System [skill: frontend-color-system] │ └─ Typography → Google Fonts [skill: frontend-google-fonts] │ └─► Quality ├─ Code checks → Debug & Linting [skill: frontend-debug-linting] └─ Visual QA → Playwright [skill: frontend-playwright]
- Project Setup Checklist
New Next.js project
npx create-next-app@latest my-app --typescript --tailwind --eslint --app
Essential dependencies
npm install clsx tailwind-merge framer-motion npm install -D prettier eslint-config-prettier
UI foundation
npx shadcn@latest init npx shadcn@latest add button card input dialog
Recommended Structure
src/ ├── app/ # Next.js App Router │ ├── layout.tsx # Root layout + fonts │ ├── page.tsx # Home page │ └── (routes)/ # Route groups ├── components/ │ ├── ui/ # shadcn components │ ├── magicui/ # Magic UI components │ └── [feature]/ # Feature components ├── lib/ │ ├── fonts.ts # Font configuration │ ├── utils.ts # cn() helper │ └── constants.ts # App constants ├── styles/ │ └── globals.css # Tailwind + CSS vars └── public/ ├── animations/ # .lottie, .riv files ├── icons/ # Downloaded SVGs └── images/ # Static images
- Component Selection Guide
UI Components Decision Tree
Need a component? │ ├─► Form element (input, select, checkbox) │ └─► shadcn/ui — accessible, unstyled base │ ├─► Data display (table, card, list) │ └─► shadcn/ui — consistent patterns │ ├─► Marketing/Landing page │ ├─► Stats/numbers → Magic UI: NumberTicker │ ├─► Logo carousel → Magic UI: Marquee │ ├─► Feature grid → Magic UI: BentoGrid │ ├─► Hero spotlight → Aceternity: Spotlight, Aurora │ ├─► 3D hover cards → Aceternity: 3DCard │ ├─► Text reveal → Aceternity: TextGenerateEffect │ └─► Device mockup → Magic UI: Safari, iPhone │ └─► Interactive element ├─► Simple hover/focus → Tailwind transitions ├─► Complex entrance → Framer Motion └─► State machine → Rive
Animation Decision Tree
Animation needed? │ ├─► Does it react to user input? │ ├─ NO → Lottie (just plays) │ └─ YES → Does it have multiple states? │ ├─ Simple hover → CSS/Framer Motion │ └─ Complex states → Rive │ ├─► What type? │ ├─ Loading spinner → Lottie │ ├─ Success/error → Lottie │ ├─ Empty state illustration → Lottie │ ├─ Animated toggle/checkbox → Rive │ ├─ Progress driven by data → Rive │ └─ Hero background effect → Aceternity
Quick Reference:
Need Solution
Loader spinner Lottie
Success checkmark Lottie
Animated button Rive
Data-driven progress Rive
Hero spotlight Aceternity
Number ticker Magic UI
- Styling Best Practices
Tailwind Patterns
// ✅ Use cn() for conditional classes import { cn } from "@/lib/utils"
<button className={cn( "px-4 py-2 rounded-md font-medium", "bg-primary text-primary-foreground", "hover:bg-primary/90 transition-colors", disabled && "opacity-50 cursor-not-allowed" )}>
// ✅ Responsive: mobile-first <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
// ✅ Dark mode with CSS variables <div className="bg-background text-foreground">
// ❌ Avoid arbitrary values when Tailwind has it <div className="mt-[16px]"> // Bad <div className="mt-4"> // Good
Color System Setup
→ See [frontend-color-system] for full guide
/* globals.css - shadcn theme structure / :root { --background: 0 0% 100%; --foreground: 240 10% 3.9%; --primary: 239 84% 67%; --primary-foreground: 0 0% 98%; / ... */ }
.dark { --background: 240 10% 3.9%; --foreground: 0 0% 98%; /* ... */ }
Typography Setup
→ See [frontend-google-fonts] for font pairings
// lib/fonts.ts import { Inter, Plus_Jakarta_Sans } from 'next/font/google'
export const inter = Inter({ subsets: ['latin'], display: 'swap', variable: '--font-inter', })
export const jakarta = Plus_Jakarta_Sans({ subsets: ['latin'], display: 'swap', variable: '--font-jakarta', })
// app/layout.tsx
<html className={${inter.variable} ${jakarta.variable}}>
// tailwind.config.ts fontFamily: { sans: ['var(--font-inter)'], display: ['var(--font-jakarta)'], }
Font Pairing Presets:
Project Type Heading Body
Modern SaaS Plus Jakarta Sans Inter
Corporate Source Sans 3 Source Serif 4
Editorial Playfair Display Lora
Dev Tools Geist Inter
- Assets Strategy
Icons
→ See [frontend-iconify] for full API
// Recommended: @iconify/react with Lucide set import { Icon } from '@iconify/react'
<Icon icon="lucide:home" className="w-5 h-5" />
// Or download SVGs for performance curl -o ./public/icons/home.svg "https://api.iconify.design/lucide/home.svg"
Images — FREE FIRST
→ See [frontend-image-generation] for all options
ALWAYS FREE FIRST: Avatars: DiceBear, Boring Avatars, UI Avatars Photos: Unsplash, Picsum Illustrations: unDraw, Storyset Backgrounds: Haikei, Hero Patterns
AI GENERATION ONLY WHEN:
- Custom branded asset needed
- No suitable free alternative
- User explicitly requests
// Avatar with fallback
const fallback = https://api.dicebear.com/7.x/lorelei/svg?seed=${name}
<img src={src || fallback} onError={e => e.target.src = fallback} />
// Placeholder photo <img src="https://source.unsplash.com/800x600/?technology" />
- SSR & Hydration Rules
Critical for Next.js App Router:
// 1. Client components need directive 'use client'
// 2. Browser-only code → useEffect const [mounted, setMounted] = useState(false) useEffect(() => setMounted(true), []) if (!mounted) return <Skeleton />
// 3. Heavy animations → dynamic import import dynamic from 'next/dynamic' const Globe = dynamic(() => import('@/components/globe'), { ssr: false })
// 4. Window/document access → check first if (typeof window !== 'undefined') { // browser code }
Components requiring 'use client':
-
All Aceternity components
-
All Magic UI animated components
-
Lottie/Rive players
-
Anything using useState, useEffect, event handlers
- Performance Checklist
Images: ✓ Use next/image with proper sizing ✓ Add priority to LCP images ✓ Lazy load below-fold images
Fonts: ✓ Use next/font (auto self-hosted) ✓ Only 'latin' subset unless needed ✓ display: 'swap' always
Animations: ✓ Reduce particles on mobile ✓ Respect prefers-reduced-motion ✓ Pause when not in viewport
Components: ✓ Dynamic import heavy components ✓ Lazy load below-fold sections ✓ Memoize expensive renders
// Reduced motion check const prefersReducedMotion = window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches
// Viewport visibility import { useInView } from 'react-intersection-observer' const { ref, inView } = useInView({ threshold: 0.1 }) useEffect(() => { inView ? animation.play() : animation.pause() }, [inView])
- Quality Gates
Before Every Delivery
→ See [frontend-debug-linting] and [frontend-playwright]
CODE CHECKS (required): npm run lint → 0 errors npm run typecheck → 0 errors npm run format → clean
VISUAL QA (required):
- npm run dev
- browser_navigate → page
- browser_take_screenshot → looks correct
- browser_console_messages { onlyErrors: true } → EMPTY
- browser_resize { width: 375 } → mobile works
Common Issues Quick Fix
TypeScript: "Type 'X' not assignable to 'Y'" → Fix type or add assertion "Object possibly undefined" → Add ?. or ?? fallback
React: "Missing useEffect dependencies" → Add deps or useCallback "Each child needs unique key" → Add key={item.id}
Hydration: "Text content mismatch" → 'use client' + mounted check "Hydration failed" → dynamic import with ssr: false
Console: "Failed to fetch" → Check API/network "Cannot read property of undefined" → Add loading state
- Common Patterns
Responsive Container
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl"> {children} </div>
Section Spacing
<section className="py-16 md:py-24 lg:py-32"> <div className="container"> <h2 className="text-3xl md:text-4xl lg:text-5xl font-bold mb-8"> Title </h2> </div> </section>
Card Grid
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {items.map(item => ( <Card key={item.id}> <CardHeader> <CardTitle>{item.title}</CardTitle> </CardHeader> <CardContent>{item.content}</CardContent> </Card> ))} </div>
Loading State
function Component() { const [data, setData] = useState(null) const [loading, setLoading] = useState(true)
if (loading) return <Skeleton className="h-32 w-full" /> if (!data) return <EmptyState /> return <Content data={data} /> }
- Skill Reference Map
Task Primary Skill When to Use
Dramatic hero effects frontend-aceternity Spotlight, aurora, 3D cards
Color palette/theme frontend-color-system Brand colors, dark mode
Linting & debugging frontend-debug-linting Before every delivery
Typography frontend-google-fonts Font setup, pairings
Icons frontend-iconify Search & integrate icons
Images & avatars frontend-image-generation FREE assets first
Simple animations frontend-lottie Loaders, feedback
SaaS components frontend-magic-ui Tickers, marquees, mockups
Visual testing frontend-playwright Screenshot verification
Interactive animations frontend-rive State-driven animations
- Quick Start Templates
Landing Page Hero
'use client' import { Spotlight } from '@/components/ui/spotlight' import { FlipWords } from '@/components/ui/flip-words'
export function Hero() { return ( <section className="relative h-screen bg-black overflow-hidden"> <Spotlight className="absolute -top-40 left-0" fill="white" /> <div className="relative z-10 container flex flex-col items-center justify-center h-full text-center"> <h1 className="text-4xl md:text-6xl font-bold text-white mb-6"> Build <FlipWords words={["faster", "better", "smarter"]} /> apps </h1> <p className="text-xl text-gray-400 max-w-2xl mb-8"> Description text here </p> <Button size="lg">Get Started</Button> </div> </section> ) }
Stats Section
'use client' import { NumberTicker } from '@/components/magicui/number-ticker'
const stats = [ { value: 10000, label: 'Users', suffix: '+' }, { value: 99.9, label: 'Uptime', suffix: '%' }, { value: 50, label: 'Countries', suffix: '+' }, ]
export function Stats() { return ( <section className="py-16 bg-muted"> <div className="container grid grid-cols-1 md:grid-cols-3 gap-8 text-center"> {stats.map(stat => ( <div key={stat.label}> <div className="text-4xl font-bold"> <NumberTicker value={stat.value} /> {stat.suffix} </div> <div className="text-muted-foreground">{stat.label}</div> </div> ))} </div> </section> ) }
External Resources
-
shadcn/ui: https://ui.shadcn.com
-
Tailwind CSS: https://tailwindcss.com/docs
-
Next.js: https://nextjs.org/docs
-
Framer Motion: https://www.framer.com/motion
-
Magic UI: https://magicui.design
-
Aceternity: https://ui.aceternity.com
-
LottieFiles: https://lottiefiles.com
-
Rive: https://rive.app
For latest API of any library → use context7 skill