UI Skills - Frontend Development Guide
Overview
This skill provides comprehensive guidance for modern UI/frontend development across multiple frameworks and libraries. Use this skill whenever you're working on user interfaces, components, styling, or interactive features.
When This Skill Activates
This skill automatically activates when:
- •Building or modifying UI components (React, Vue, Svelte, etc.)
- •Working with Next.js, Create React App, Vite, or other frontend tooling
- •Implementing styles with Tailwind CSS, CSS Modules, or styled-components
- •Creating layouts, forms, navigation, or interactive elements
- •Implementing responsive design or accessibility features
- •Working with state management (React hooks, Zustand, Redux, Pinia, etc.)
Design System Selection
IMPORTANT: Before starting ANY new UI work, you should invoke the ui-portfolio-design skill to help the user select a design direction. This ensures consistent, intentional aesthetics from the start.
To invoke the portfolio selection:
- •Detect when starting new UI work (not modifying existing components)
- •Present the portfolio selection from
/docs/ui-portfolio/index.html - •Once user selects a direction, apply that design system consistently
Skip portfolio selection only when:
- •Modifying existing components (maintain current style)
- •User explicitly requests a specific style
- •Working in a codebase with established design system
Core Principles
1. Component Architecture
Single Responsibility
- •Each component should do one thing well
- •Extract complex logic into custom hooks or composables
- •Keep components small and focused (< 200 lines)
Composition Over Complexity
// Good: Composable, reusable
<Card>
<CardHeader>
<CardTitle>Dashboard</CardTitle>
</CardHeader>
<CardContent>
{children}
</CardContent>
</Card>
// Avoid: Monolithic with too many props
<Card
title="Dashboard"
showHeader
headerAlign="left"
contentPadding="large"
// ... 20 more props
/>
Props Design
- •Use discriminated unions for variants
- •Prefer
childrenover render props when possible - •Make common use cases simple, advanced use cases possible
- •Use TypeScript for prop validation
2. Accessibility (a11y)
Always Include
- •Semantic HTML elements (
<button>,<nav>,<main>, etc.) - •ARIA labels where needed (
aria-label,aria-describedby) - •Keyboard navigation (Tab, Enter, Escape, Arrow keys)
- •Focus management (visible focus states, focus trapping in modals)
- •Color contrast ratios (WCAG AA minimum: 4.5:1 for text)
Common Patterns
// Accessible button
<button
onClick={handleClick}
aria-label="Close dialog"
aria-pressed={isActive}
>
<Icon aria-hidden="true" />
Close
</button>
// Accessible form input
<div>
<label htmlFor="email" className="block mb-2">
Email Address
</label>
<input
id="email"
type="email"
aria-describedby="email-hint"
aria-invalid={hasError}
/>
<span id="email-hint" className="text-sm text-gray-600">
We'll never share your email
</span>
</div>
// Skip to main content
<a href="#main-content" className="sr-only focus:not-sr-only">
Skip to main content
</a>
<main id="main-content">
{/* content */}
</main>
3. Responsive Design
Mobile-First Approach
- •Start with mobile layout, enhance for larger screens
- •Use Tailwind breakpoints:
sm:,md:,lg:,xl:,2xl: - •Test at breakpoints: 320px, 768px, 1024px, 1440px
Fluid Typography & Spacing
/* Use clamp for fluid sizing */ font-size: clamp(1rem, 2vw + 0.5rem, 2rem); /* Or Tailwind responsive utilities */ <h1 className="text-2xl sm:text-3xl lg:text-5xl">
Container Queries (when supported)
@container (min-width: 400px) {
.card { grid-template-columns: 1fr 1fr; }
}
4. State Management
Local State First
- •Use component state (
useState,ref) for UI-only state - •Lift state only when needed by multiple components
- •Use context sparingly (avoid overuse)
Server State
- •Use TanStack Query (React Query) for server data
- •Implement optimistic updates for better UX
- •Handle loading, error, and success states
Form State
- •React Hook Form for complex forms
- •Controlled vs uncontrolled inputs (prefer uncontrolled with refs)
- •Validation with Zod or Yup
// Good: Server state with TanStack Query
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
})
// Good: Form state with React Hook Form
const { register, handleSubmit, formState: { errors } } = useForm()
5. Performance
Lazy Loading
// Code splitting
const Dashboard = lazy(() => import('./Dashboard'))
// Image optimization
<Image
src="/hero.jpg"
alt="Hero"
loading="lazy"
width={1200}
height={600}
/>
Memoization
- •Use
useMemofor expensive calculations - •Use
useCallbackfor stable function references - •Use
memo()for expensive components - •Don't over-optimize - profile first
Virtual Lists
- •Use react-virtual for long lists (> 100 items)
- •Implement infinite scroll with Intersection Observer
6. Styling Best Practices
Tailwind CSS
- •Use
@applysparingly (components only) - •Prefer utility classes in JSX
- •Extract repeated patterns to components, not
@apply - •Use arbitrary values when needed:
w-[347px]
Component Variants
// Using class-variance-authority (cva)
const buttonVariants = cva(
"rounded font-medium transition-colors", // base
{
variants: {
variant: {
default: "bg-blue-600 text-white hover:bg-blue-700",
outline: "border border-gray-300 hover:bg-gray-50",
ghost: "hover:bg-gray-100",
},
size: {
sm: "px-3 py-1.5 text-sm",
md: "px-4 py-2 text-base",
lg: "px-6 py-3 text-lg",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
}
)
CSS Modules (when not using Tailwind)
- •Use for component-scoped styles
- •Name files
Component.module.css - •Compose styles with
composes
7. Component Patterns
Compound Components
// API that reads well
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
</TabsList>
<TabsContent value="overview">
{/* content */}
</TabsContent>
</Tabs>
Render Props (when composition isn't enough)
<DataFetcher url="/api/users">
{({ data, loading, error }) => (
loading ? <Spinner /> : <UserList users={data} />
)}
</DataFetcher>
Slots Pattern (React, Vue)
// React
<Layout
header={<Header />}
sidebar={<Sidebar />}
footer={<Footer />}
>
{children}
</Layout>
8. Error Handling
Error Boundaries (React)
<ErrorBoundary fallback={<ErrorPage />}>
<App />
</ErrorBoundary>
Error States in UI
if (error) {
return (
<Alert variant="error">
<AlertTitle>Failed to load data</AlertTitle>
<AlertDescription>
{error.message}
<Button onClick={retry}>Try Again</Button>
</AlertDescription>
</Alert>
)
}
9. Animation & Transitions
Framer Motion (React)
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
{content}
</motion.div>
CSS Transitions (Tailwind)
<div className="transition-all duration-200 hover:scale-105">
Reduced Motion
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
10. Testing
Component Tests
- •Test user interactions, not implementation
- •Use Testing Library queries (
getByRole,getByLabelText) - •Test accessibility (keyboard nav, screen reader labels)
Visual Regression
- •Consider Storybook + Chromatic for component libraries
- •Screenshot tests for critical UI
Framework-Specific Guidance
React / Next.js
File Organization
src/
components/
ui/ # Reusable UI primitives (Button, Input, Card)
features/ # Feature-specific components
hooks/ # Custom hooks
lib/ # Utilities, API clients
app/ # Next.js app router
Server Components (Next.js 13+)
- •Default to Server Components
- •Use Client Components only when needed (interactivity, hooks)
- •Pass server data as props to client components
Data Fetching
// Server Component
async function Page() {
const data = await fetch('https://api.example.com/data')
return <Dashboard data={data} />
}
// Client Component with TanStack Query
'use client'
function Dashboard() {
const { data } = useQuery(...)
return <div>{data}</div>
}
Vue / Nuxt
Composition API
- •Prefer
<script setup>syntax - •Use composables for reusable logic
- •Leverage auto-imports
Reactivity
// Reactive state
const state = reactive({ count: 0 })
// Computed properties
const doubled = computed(() => state.count * 2)
// Watch for changes
watch(() => state.count, (newVal) => {
console.log(newVal)
})
Svelte / SvelteKit
Reactivity
<script>
let count = 0
$: doubled = count * 2 // Reactive declaration
</script>
<button on:click={() => count++}>
Count: {count}, Doubled: {doubled}
</button>
Stores
// Writable store
const count = writable(0)
// Derived store
const doubled = derived(count, $count => $count * 2)
// Auto-subscription in components
<script>
import { count } from './stores'
</script>
<div>{$count}</div>
Common Anti-Patterns to Avoid
❌ Don't
Over-Abstraction
// TOO GENERIC
<FlexBox direction="column" justify="center" align="items" gap={4}>
Prop Drilling
- •Passing props through 3+ levels
- •Solution: Context, composition, or state management
Inline Functions in JSX (if causing perf issues)
// Can cause re-renders
<button onClick={() => handleClick(id)}>
Magic Numbers
// Bad <div className="w-[347px] h-[291px]"> // Good <div className="w-80 h-72"> // or semantic sizes
Ignoring Loading States
// Bad: Just shows nothing while loading
{data && <UserList users={data} />}
// Good: Show skeleton or spinner
{isLoading ? <Skeleton /> : <UserList users={data} />}
✅ Do
Use Semantic HTML
<button> for actions, not <div onClick> <a> for navigation, not <div onClick> <nav>, <main>, <article>, <section>
Keep Props Simple
// Good: Clear, typed props
interface ButtonProps {
variant?: 'default' | 'outline' | 'ghost'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
onClick?: () => void
children: React.ReactNode
}
Handle Edge Cases
- •Empty states (no data)
- •Loading states (fetching data)
- •Error states (failed request)
- •Disabled states (form submission)
Tools & Libraries
Essential
- •Tailwind CSS - Utility-first CSS
- •TanStack Query - Server state management
- •React Hook Form - Form state
- •Zod - Schema validation
UI Components
- •shadcn/ui - Copy-paste components
- •Radix UI - Headless components
- •Headless UI - Tailwind-specific
Animation
- •Framer Motion - React animations
- •Auto-animate - Drop-in animations
Icons
- •Lucide React - Icon library
- •Heroicons - Tailwind icons
Workflow
Starting New UI Work
- •
Check for existing design system
- •Look for existing components in
components/ui/ - •Check for Tailwind config, color schemes
- •Review any design documentation
- •Look for existing components in
- •
If no design system exists
- •Invoke
ui-portfolio-designskill - •Present portfolio selection to user
- •Load chosen design system specifications
- •Invoke
- •
Build components
- •Start with semantic HTML
- •Apply styles (Tailwind or CSS Modules)
- •Add interactivity (event handlers, state)
- •Implement accessibility features
- •Handle loading/error states
- •
Test & refine
- •Test keyboard navigation
- •Test at different screen sizes
- •Test with screen reader (if possible)
- •Review with user
Modifying Existing UI
- •
Understand current patterns
- •Read existing component code
- •Identify naming conventions
- •Note styling approach (Tailwind, CSS Modules, etc.)
- •
Maintain consistency
- •Match existing component structure
- •Use same styling patterns
- •Follow established naming conventions
- •
Improve incrementally
- •Fix accessibility issues when found
- •Suggest refactors only when asked
- •Don't rewrite working code without reason
Best Practices Summary
- •Accessibility First - Always include semantic HTML, ARIA labels, keyboard nav
- •Mobile First - Start mobile, enhance for desktop
- •Performance - Lazy load, code split, optimize images
- •Composition - Small, reusable components
- •Type Safety - Use TypeScript for props and state
- •Error Handling - Always handle loading, error, empty states
- •Testing - Test user behavior, not implementation
- •Consistency - Follow existing patterns in the codebase
When to Ask for Clarification
- •Design direction unclear - Invoke portfolio selection
- •Accessibility requirements - Ask about target WCAG level
- •Browser support - Ask about target browsers/devices
- •Performance constraints - Ask about expected data volume
- •State management - Ask if existing solution is in place
Remember: Before starting new UI work, consider invoking the ui-portfolio-design skill to select a design direction. This ensures intentional, cohesive aesthetics from the start.