Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.
.claude/skills/davila7-frontend-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-22 | ✓→✗ | ▼ Worse | 155% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 192% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 233% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 148% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 38% | 0% |
Modern frontend patterns for React, Next.js, and performant user interfaces.
typescript// ✅ GOOD: Component composition interface CardProps { children: React.ReactNode variant?: 'default' | 'outlined' } export function Card({ children, variant = 'default' }: CardProps) { return <div className={`card card-${variant}`}>{children}</div> } export function CardHeader({ children }: { children: React.ReactNode }) { return <div className="card-header">{children}</div> } export function CardBody({ children }: { children: React.ReactNode }) { return <div className="card-body">{children}</div> } // Usage <Card> <CardHeader>Title</CardHeader> <CardBody>Content</CardBody> </Card>
typescriptinterface 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>
typescriptinterface 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>
typescriptexport 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()
typescriptinterface 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) } )
typescriptexport 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])
typescriptinterface 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 }
typescript// ✅ 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> ) })
typescriptimport { 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> ) }
typescriptimport { 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> ) }
typescriptinterface 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 && <span className="error">{errors.name}</span>} {/* Other fields */} <button type="submit">Create Market</button> </form> ) }
typescriptinterface 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>
typescriptimport { 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 && ( <> <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> ) }
typescriptexport 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> ) }
typescriptexport 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' && onClose()} > {children} </div> ) : null }
Remember: Modern frontend patterns enable maintainable, performant user interfaces. Choose patterns that fit your project complexity.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 18,532 | 10,767 | -42% | 1 | 1 | 0% | 4,474 | 6,421 | +44% | 0 | 0 | — |
case-02 | pass→pass | 9,962 | 9,557 | -4% | 1 | 1 | 0% | 2,008 | 5,864 | +192% | 0 | 0 | — |
case-03 | pass→pass | 8,802 | 10,239 | +16% | 1 | 1 | 0% | 1,909 | 6,348 | +233% | 0 | 0 | — |
case-04 | pass→pass | 12,240 | 11,441 | -7% | 1 | 1 | 0% | 2,560 | 6,361 | +148% | 0 | 0 | — |
case-05 | pass→pass | 21,643 | 13,552 | -37% | 1 | 1 | 0% | 5,246 | 7,225 | +38% | 0 | 0 | — |
case-06 | fail→fail | 19,468 | 20,412 | +5% | 1 | 1 | 0% | 4,279 | 8,406 | +96% | 0 | 0 | — |
case-07 | pass→pass | 12,134 | 11,315 | -7% | 1 | 1 | 0% | 2,830 | 6,614 | +134% | 0 | 0 | — |
case-08 | pass→pass | 13,142 | 10,825 | -18% | 1 | 1 | 0% | 2,705 | 6,121 | +126% | 0 | 0 | — |
case-09 | pass→pass | 5,963 | 6,445 | +8% | 1 | 1 | 0% | 1,422 | 5,324 | +274% | 0 | 0 | — |
case-10 | pass→pass | 14,563 | 15,208 | +4% | 1 | 1 | 0% | 3,389 | 8,006 | +136% | 0 | 0 | — |
case-11 | fail→fail | 13,923 | 11,732 | -16% | 1 | 1 | 0% | 3,142 | 6,805 | +117% | 0 | 0 | — |
case-12 | pass→pass | 14,892 | 14,410 | -3% | 1 | 1 | 0% | 3,083 | 7,014 | +128% | 0 | 0 | — |
case-13 | pass→pass | 12,910 | 7,862 | -39% | 1 | 1 | 0% | 3,025 | 5,573 | +84% | 0 | 0 | — |
case-14 | pass→pass | 13,449 | 11,945 | -11% | 1 | 1 | 0% | 3,253 | 6,663 | +105% | 0 | 0 | — |
case-15 | pass→pass | 12,366 | 10,824 | -12% | 1 | 1 | 0% | 2,960 | 6,331 | +114% | 0 | 0 | — |
case-16 | fail→fail | 11,915 | 10,096 | -15% | 1 | 1 | 0% | 2,676 | 6,117 | +129% | 0 | 0 | — |
case-17 | pass→pass | 13,221 | 11,083 | -16% | 1 | 1 | 0% | 3,061 | 6,709 | +119% | 0 | 0 | — |
case-18 | fail→fail | 16,219 | 17,324 | +7% | 1 | 1 | 0% | 4,036 | 8,189 | +103% | 0 | 0 | — |
case-19 | pass→pass | 16,645 | 19,480 | +17% | 1 | 1 | 0% | 3,905 | 8,496 | +118% | 0 | 0 | — |
case-20 | pass→pass | 11,184 | 9,697 | -13% | 1 | 1 | 0% | 2,463 | 5,962 | +142% | 0 | 0 | — |
case-21 | pass→pass | 11,013 | 11,595 | +5% | 1 | 1 | 0% | 2,408 | 6,449 | +168% | 0 | 0 | — |
case-22 | pass→fail | 13,941 | 14,557 | +4% | 1 | 1 | 0% | 2,855 | 7,274 | +155% | 0 | 0 | — |
case-23 | pass→pass | 11,982 | 12,629 | +5% | 1 | 1 | 0% | 2,206 | 6,497 | +195% | 0 | 0 | — |
case-24 | pass→pass | 11,879 | 11,274 | -5% | 1 | 1 | 0% | 2,285 | 6,344 | +178% | 0 | 0 | — |
case-25 | pass→pass | 11,234 | 11,328 | +1% | 1 | 1 | 0% | 2,177 | 6,242 | +187% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of -4 percentage points is the difference between those two pass rates over the 25 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Other measured skills in the registry, with their headline benchmark lift.