frontend-patterns

React and Next.js patterns for component composition, state management, performance, and accessibility. Covers component patterns including composition, compound components, and render props for flexible, reusable UI architecture Includes custom hooks for state management, async data fetching, debouncing, and context-based state with reducers Performance optimization techniques: memoization, code splitting with lazy loading, and list virtualization for large datasets Form handling with validation, error boundaries for graceful error recovery, and animation patterns using Framer Motion Accessibility patterns for keyboard navigation and focus management to ensure inclusive user experiences

INSTALLATION
npx skills add https://github.com/davila7/claude-code-templates --skill frontend-patterns
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2b

export function CardHeader({ children }: { children: React.ReactNode }) {

return {children}

}

export function CardBody({ children }: { children: React.ReactNode }) {

return {children}

}

// Usage

Title

Content

### Compound Components

interface TabsContextValue {

activeTab: string

setActiveTab: (tab: string) => void

}

const TabsContext = createContext<TabsContextValue | undefined>(undefined)

export function Tabs({ children, defaultTab }: {

children: React.ReactNode

defaultTab: string

}) {

const [activeTab, setActiveTab] = useState(defaultTab)

return (

<TabsContext.Provider value={{ activeTab, setActiveTab }}>

{children}

</TabsContext.Provider>

)

}

export function TabList({ children }: { children: React.ReactNode }) {

return <div className="tab-list">{children}</div>

}

export function Tab({ id, children }: { id: string, children: React.ReactNode }) {

const context = useContext(TabsContext)

if (!context) throw new Error('Tab must be used within Tabs')

return (

<button

className={context.activeTab === id ? 'active' : ''}

onClick={() => context.setActiveTab(id)}

>

{children}

</button>

)

}

// Usage

<Tabs defaultTab="overview">

<TabList>

<Tab id="overview">Overview</Tab>

<Tab id="details">Details</Tab>

</TabList>

</Tabs>


### Render Props Pattern

interface DataLoaderProps<T> {

url: string

children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode

}

export function DataLoader<T>({ url, children }: DataLoaderProps<T>) {

const [data, setData] = useState<T | null>(null)

const [loading, setLoading] = useState(true)

const [error, setError] = useState<Error | null>(null)

useEffect(() => {

fetch(url)

.then(res => res.json())

.then(setData)

.catch(setError)

.finally(() => setLoading(false))

}, [url])

return <>{children(data, loading, error)}</>

}

// Usage

<DataLoader<Market[]> url="/api/markets">

{(markets, loading, error) => {

if (loading) return <Spinner />

if (error) return <Error error={error} />

return <MarketList markets={markets!} />

}}

</DataLoader>


## Custom Hooks Patterns

### State Management Hook

export function useToggle(initialValue = false): [boolean, () => void] {

const [value, setValue] = useState(initialValue)

const toggle = useCallback(() => {

setValue(v => !v)

}, [])

return [value, toggle]

}

// Usage

const [isOpen, toggleOpen] = useToggle()


### Async Data Fetching Hook

interface UseQueryOptions<T> {

onSuccess?: (data: T) => void

onError?: (error: Error) => void

enabled?: boolean

}

export function useQuery<T>(

key: string,

fetcher: () => Promise<T>,

options?: UseQueryOptions<T>

) {

const [data, setData] = useState<T | null>(null)

const [error, setError] = useState<Error | null>(null)

const [loading, setLoading] = useState(false)

const refetch = useCallback(async () => {

setLoading(true)

setError(null)

try {

const result = await fetcher()

setData(result)

options?.onSuccess?.(result)

} catch (err) {

const error = err as Error

setError(error)

options?.onError?.(error)

} finally {

setLoading(false)

}

}, [fetcher, options])

useEffect(() => {

if (options?.enabled !== false) {

refetch()

}

}, [key, refetch, options?.enabled])

return { data, error, loading, refetch }

}

// Usage

const { data: markets, loading, error, refetch } = useQuery(

'markets',

() => fetch('/api/markets').then(r => r.json()),

{

onSuccess: data => console.log('Fetched', data.length, 'markets'),

onError: err => console.error('Failed:', err)

}

)


### Debounce Hook

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

}

// Usage

const [searchQuery, setSearchQuery] = useState('')

const debouncedQuery = useDebounce(searchQuery, 500)

useEffect(() => {

if (debouncedQuery) {

performSearch(debouncedQuery)

}

}, [debouncedQuery])


## State Management Patterns

### Context + Reducer Pattern

interface State {

markets: Market[]

selectedMarket: Market | null

loading: boolean

}

type Action =

| { type: 'SET_MARKETS'; payload: Market[] }

| { type: 'SELECT_MARKET'; payload: Market }

| { type: 'SET_LOADING'; payload: boolean }

function reducer(state: State, action: Action): State {

switch (action.type) {

case 'SET_MARKETS':

return { ...state, markets: action.payload }

case 'SELECT_MARKET':

return { ...state, selectedMarket: action.payload }

case 'SET_LOADING':

return { ...state, loading: action.payload }

default:

return state

}

}

const MarketContext = createContext<{

state: State

dispatch: Dispatch<Action>

} | undefined>(undefined)

export function MarketProvider({ children }: { children: React.ReactNode }) {

const [state, dispatch] = useReducer(reducer, {

markets: [],

selectedMarket: null,

loading: false

})

return (

<MarketContext.Provider value={{ state, dispatch }}>

{children}

</MarketContext.Provider>

)

}

export function useMarkets() {

const context = useContext(MarketContext)

if (!context) throw new Error('useMarkets must be used within MarketProvider')

return context

}


## Performance Optimization

### Memoization

// ✅ useMemo for expensive computations

const sortedMarkets = useMemo(() => {

return markets.sort((a, b) => b.volume - a.volume)

}, [markets])

// ✅ useCallback for functions passed to children

const handleSearch = useCallback((query: string) => {

setSearchQuery(query)

}, [])

// ✅ React.memo for pure components

export const MarketCard = React.memo<MarketCardProps>(({ market }) => {

return (

<div className="market-card">

<h3>{market.name}</h3>

<p>{market.description}</p>

</div>

)

})


### Code Splitting &#x26; Lazy Loading

import { lazy, Suspense } from 'react'

// ✅ Lazy load heavy components

const HeavyChart = lazy(() => import('./HeavyChart'))

const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))

export function Dashboard() {

return (

<div>

<Suspense fallback={<ChartSkeleton />}>

<HeavyChart data={data} />

</Suspense>

<Suspense fallback={null}>

<ThreeJsBackground />

</Suspense>

</div>

)

}


### Virtualization for Long Lists

import { useVirtualizer } from '@tanstack/react-virtual'

export function VirtualMarketList({ markets }: { markets: Market[] }) {

const parentRef = useRef<HTMLDivElement>(null)

const virtualizer = useVirtualizer({

count: markets.length,

getScrollElement: () => parentRef.current,

estimateSize: () => 100, // Estimated row height

overscan: 5 // Extra items to render

})

return (

<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>

<div

style={{

height: ${virtualizer.getTotalSize()}px,

position: 'relative'

}}

>

{virtualizer.getVirtualItems().map(virtualRow => (

<div

key={virtualRow.index}

style={{

position: 'absolute',

top: 0,

left: 0,

width: '100%',

height: ${virtualRow.size}px,

transform: translateY(${virtualRow.start}px)

}}

>

<MarketCard market={markets[virtualRow.index]} />

</div>

))}

</div>

</div>

)

}


## Form Handling Patterns

### Controlled Form with Validation

interface FormData {

name: string

description: string

endDate: string

}

interface FormErrors {

name?: string

description?: string

endDate?: string

}

export function CreateMarketForm() {

const [formData, setFormData] = useState<FormData>({

name: '',

description: '',

endDate: ''

})

const [errors, setErrors] = useState<FormErrors>({})

const validate = (): boolean => {

const newErrors: FormErrors = {}

if (!formData.name.trim()) {

newErrors.name = 'Name is required'

} else if (formData.name.length > 200) {

newErrors.name = 'Name must be under 200 characters'

}

if (!formData.description.trim()) {

newErrors.description = 'Description is required'

}

if (!formData.endDate) {

newErrors.endDate = 'End date is required'

}

setErrors(newErrors)

return Object.keys(newErrors).length === 0

}

const handleSubmit = async (e: React.FormEvent) => {

e.preventDefault()

if (!validate()) return

try {

await createMarket(formData)

// Success handling

} catch (error) {

// Error handling

}

}

return (

<form onSubmit={handleSubmit}>

<input

value={formData.name}

onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}

placeholder="Market name"

/>

{errors.name &#x26;&#x26; <span className="error">{errors.name}</span>}

{/ Other fields /}

<button type="submit">Create Market</button>

</form>

)

}


## Error Boundary Pattern

interface ErrorBoundaryState {

hasError: boolean

error: Error | null

}

export class ErrorBoundary extends React.Component<

{ children: React.ReactNode },

ErrorBoundaryState

{

state: ErrorBoundaryState = {

hasError: false,

error: null

}

static getDerivedStateFromError(error: Error): ErrorBoundaryState {

return { hasError: true, error }

}

componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {

console.error('Error boundary caught:', error, errorInfo)

}

render() {

if (this.state.hasError) {

return (

<div className="error-fallback">

<h2>Something went wrong</h2>

<p>{this.state.error?.message}</p>

<button onClick={() => this.setState({ hasError: false })}>

Try again

</button>

</div>

)

}

return this.props.children

}

}

// Usage

<ErrorBoundary>

<App />

</ErrorBoundary>


## Animation Patterns

### Framer Motion Animations

import { motion, AnimatePresence } from 'framer-motion'

// ✅ List animations

export function AnimatedMarketList({ markets }: { markets: Market[] }) {

return (

<AnimatePresence>

{markets.map(market => (

<motion.div

key={market.id}

initial={{ opacity: 0, y: 20 }}

animate={{ opacity: 1, y: 0 }}

exit={{ opacity: 0, y: -20 }}

transition={{ duration: 0.3 }}

>

<MarketCard market={market} />

</motion.div>

))}

</AnimatePresence>

)

}

// ✅ Modal animations

export function Modal({ isOpen, onClose, children }: ModalProps) {

return (

<AnimatePresence>

{isOpen &#x26;&#x26; (

<>

<motion.div

className="modal-overlay"

initial={{ opacity: 0 }}

animate={{ opacity: 1 }}

exit={{ opacity: 0 }}

onClick={onClose}

/>

<motion.div

className="modal-content"

initial={{ opacity: 0, scale: 0.9, y: 20 }}

animate={{ opacity: 1, scale: 1, y: 0 }}

exit={{ opacity: 0, scale: 0.9, y: 20 }}

>

{children}

</motion.div>

</>

)}

</AnimatePresence>

)

}


## Accessibility Patterns

### Keyboard Navigation

export function Dropdown({ options, onSelect }: DropdownProps) {

const [isOpen, setIsOpen] = useState(false)

const [activeIndex, setActiveIndex] = useState(0)

const handleKeyDown = (e: React.KeyboardEvent) => {

switch (e.key) {

case 'ArrowDown':

e.preventDefault()

setActiveIndex(i => Math.min(i + 1, options.length - 1))

break

case 'ArrowUp':

e.preventDefault()

setActiveIndex(i => Math.max(i - 1, 0))

break

case 'Enter':

e.preventDefault()

onSelect(options[activeIndex])

setIsOpen(false)

break

case 'Escape':

setIsOpen(false)

break

}

}

return (

<div

role="combobox"

aria-expanded={isOpen}

aria-haspopup="listbox"

onKeyDown={handleKeyDown}

>

{/ Dropdown implementation /}

</div>

)

}


### Focus Management

export function Modal({ isOpen, onClose, children }: ModalProps) {

const modalRef = useRef<HTMLDivElement>(null)

const previousFocusRef = useRef<HTMLElement | null>(null)

useEffect(() => {

if (isOpen) {

// Save currently focused element

previousFocusRef.current = document.activeElement as HTMLElement

// Focus modal

modalRef.current?.focus()

} else {

// Restore focus when closing

previousFocusRef.current?.focus()

}

}, [isOpen])

return isOpen ? (

<div

ref={modalRef}

role="dialog"

aria-modal="true"

tabIndex={-1}

onKeyDown={e => e.key === 'Escape' &#x26;&#x26; onClose()}

>

{children}

</div>

) : null

}

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card