cc-skill-frontend-patterns

Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.

INSTALLATION
npx skills add https://github.com/sickn33/antigravity-awesome-skills --skill cc-skill-frontend-patterns
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$27

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