Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Usehooks-ts
https://github.com/juliencrn/usehooks-ts
Admin
usehooks-ts is a React hook library written in TypeScript, offering a collection of reusable hooks
...
Tokens:
9,402
Snippets:
34
Trust Score:
9.7
Update:
3 weeks ago
Context
Skills
Chat
Benchmark
89.2
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# usehooks-ts usehooks-ts is a comprehensive React hooks library written in TypeScript that provides 33 production-ready custom hooks for common development patterns. Built around the DRY (Don't Repeat Yourself) principle, this library offers extensively tested, type-safe hooks that work seamlessly with React 16.8+ through React 19. The project is structured as a Turborepo monorepo using pnpm workspaces, containing the core hooks library and a Next.js documentation website with interactive demos. The library is fully tree-shakable with minimal bundle impact, SSR-compatible, and published as the npm package `usehooks-ts`. Each hook follows React best practices with proper cleanup, memoization, and composability. The project demonstrates excellent TypeScript practices, comprehensive test coverage using Vitest, and modern build tooling with dual ESM/CJS outputs via tsup. With features like cross-tab synchronization for storage hooks, custom serialization support, and SSR initialization options, usehooks-ts is designed for production environments while maintaining developer-friendly APIs and thorough documentation. ## Installation and Setup ```bash npm install usehooks-ts ``` ```typescript // Named imports (tree-shakable) import { useLocalStorage, useCounter, useMediaQuery } from 'usehooks-ts' ``` ## useLocalStorage Persist state in browser localStorage with automatic cross-tab synchronization and SSR support. ```typescript import { useLocalStorage } from 'usehooks-ts' function ShoppingCart() { // Basic usage with automatic JSON serialization const [cart, setCart, removeCart] = useLocalStorage('cart', []) // With custom serialization const [user, setUser, removeUser] = useLocalStorage( 'user', null, { serializer: (value) => JSON.stringify(value), deserializer: (value) => JSON.parse(value), initializeWithValue: false // For SSR - prevents reading localStorage on server } ) const addItem = (product) => { setCart((prevCart) => [...prevCart, product]) } const clearCart = () => { removeCart() // Removes key from localStorage and resets to initial value } return ( <div> <h2>Cart Items: {cart.length}</h2> <button onClick={() => addItem({ id: 1, name: 'Product' })}> Add Item </button> <button onClick={clearCart}>Clear Cart</button> <pre>{JSON.stringify(cart, null, 2)}</pre> </div> ) } // The hook automatically syncs across tabs - changes in one tab // are reflected in all other tabs with the same localStorage key ``` ## useCounter Manage counter state with increment, decrement, reset, and custom setter functions. ```typescript import { useCounter } from 'usehooks-ts' function CounterDemo() { const { count, increment, decrement, reset, setCount } = useCounter(0) const incrementBy = (value) => { setCount((x) => x + value) } const multiplyBy2 = () => { setCount((x) => x * 2) } const setToRandom = () => { setCount(Math.floor(Math.random() * 100)) } return ( <div> <h1>Count: {count}</h1> <button onClick={increment}>+1</button> <button onClick={decrement}>-1</button> <button onClick={() => incrementBy(5)}>+5</button> <button onClick={multiplyBy2}>Ć2</button> <button onClick={setToRandom}>Random</button> <button onClick={reset}>Reset</button> </div> ) } ``` ## useBoolean Handle boolean state with utility functions for common operations. ```typescript import { useBoolean } from 'usehooks-ts' function ModalDialog() { const { value: isOpen, setTrue: open, setFalse: close, toggle } = useBoolean(false) const { value: isLoading, setTrue: startLoading, setFalse: stopLoading } = useBoolean(false) const handleSubmit = async () => { startLoading() try { await fetch('/api/submit', { method: 'POST' }) close() } catch (error) { console.error(error) } finally { stopLoading() } } return ( <> <button onClick={open}>Open Modal</button> {isOpen && ( <div className="modal"> <div className="modal-content"> <h2>Dialog Title</h2> <button onClick={handleSubmit} disabled={isLoading}> {isLoading ? 'Submitting...' : 'Submit'} </button> <button onClick={close}>Cancel</button> <button onClick={toggle}>Toggle</button> </div> </div> )} </> ) } ``` ## useEventListener Attach event listeners to DOM elements, window, or document with automatic cleanup. ```typescript import { useRef } from 'react' import { useEventListener } from 'usehooks-ts' function EventListenerDemo() { const buttonRef = useRef(null) const containerRef = useRef(null) const documentRef = useRef(document) // Window-level event const handleScroll = (event) => { console.log('Window scrolled:', window.scrollY) } // Document-level event const handleVisibilityChange = (event) => { console.log('Tab visibility changed:', !document.hidden) } // Element-level event with options const handleClick = (event) => { console.log('Button clicked:', event.target) } const handleMouseMove = (event) => { console.log('Mouse moved in container:', event.clientX, event.clientY) } // Attach to window useEventListener('scroll', handleScroll) // Attach to document useEventListener('visibilitychange', handleVisibilityChange, documentRef) // Attach to specific element useEventListener('click', handleClick, buttonRef) // With event listener options useEventListener('mousemove', handleMouseMove, containerRef, { passive: true, capture: false }) return ( <div ref={containerRef} style={{ minHeight: '200vh', padding: '20px' }}> <button ref={buttonRef}>Click Me</button> <p>Scroll the page and check console</p> </div> ) } ``` ## useMediaQuery Track media query state changes with SSR support and system preference detection. ```typescript import { useMediaQuery } from 'usehooks-ts' function ResponsiveLayout() { const isMobile = useMediaQuery('(max-width: 768px)') const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)') const isDesktop = useMediaQuery('(min-width: 1025px)') const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)') const isPortrait = useMediaQuery('(orientation: portrait)') // With SSR support const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)', { defaultValue: false, // Default value for SSR initializeWithValue: false // Don't read on server }) return ( <div> <h1>Responsive Layout</h1> <p>Device type: {isMobile ? 'Mobile' : isTablet ? 'Tablet' : 'Desktop'}</p> <p>Orientation: {isPortrait ? 'Portrait' : 'Landscape'}</p> <p>Dark mode: {isDarkMode ? 'Enabled' : 'Disabled'}</p> <p>Reduced motion: {prefersReducedMotion ? 'Yes' : 'No'}</p> {isMobile && <MobileMenu />} {isDesktop && <DesktopSidebar />} </div> ) } ``` ## useIntersectionObserver Track element visibility using the Intersection Observer API with threshold control and freeze options. ```typescript import { useIntersectionObserver } from 'usehooks-ts' function LazyImage({ src, alt }) { // Supports both array and object destructuring const { ref, isIntersecting, entry } = useIntersectionObserver({ threshold: 0.5, // Trigger when 50% visible root: null, // viewport rootMargin: '0px', freezeOnceVisible: true, // Stop observing after first intersection onChange: (isIntersecting, entry) => { console.log('Visibility changed:', isIntersecting, entry.intersectionRatio) }, initialIsIntersecting: false }) return ( <div ref={ref}> {isIntersecting ? ( <img src={src} alt={alt} loading="lazy" /> ) : ( <div className="placeholder">Loading...</div> )} {entry && ( <p>Intersection ratio: {(entry.intersectionRatio * 100).toFixed(0)}%</p> )} </div> ) } function InfiniteScroll() { // Using array destructuring const [ref, isIntersecting] = useIntersectionObserver({ threshold: 0.1, rootMargin: '100px' // Load before scrolling to element }) useEffect(() => { if (isIntersecting) { loadMoreItems() } }, [isIntersecting]) return ( <div> {items.map(item => <Item key={item.id} {...item} />)} <div ref={ref}> {isIntersecting && <Spinner />} </div> </div> ) } ``` ## useDarkMode Manage dark mode state with system preference detection and localStorage persistence. ```typescript import { useDarkMode } from 'usehooks-ts' function ThemeToggle() { const { isDarkMode, toggle, enable, disable, set } = useDarkMode({ defaultValue: false, localStorageKey: 'app-theme', // Custom storage key initializeWithValue: true // Read from localStorage on mount }) // Apply theme to document useEffect(() => { document.documentElement.classList.toggle('dark', isDarkMode) document.documentElement.style.colorScheme = isDarkMode ? 'dark' : 'light' }, [isDarkMode]) return ( <div> <button onClick={toggle}> {isDarkMode ? 'š Dark' : 'āļø Light'} </button> <button onClick={enable}>Force Dark</button> <button onClick={disable}>Force Light</button> <button onClick={() => set(false)}>Set Light</button> <p>Current theme: {isDarkMode ? 'Dark Mode' : 'Light Mode'}</p> <p>Automatically syncs with system preference</p> </div> ) } ``` ## useDebounceCallback Create debounced callback functions with control over timing and invocation behavior. ```typescript import { useState } from 'react' import { useDebounceCallback } from 'usehooks-ts' function SearchBar() { const [searchTerm, setSearchTerm] = useState('') const [results, setResults] = useState([]) const debouncedSearch = useDebounceCallback( async (term) => { if (!term) { setResults([]) return } try { const response = await fetch(`/api/search?q=${term}`) const data = await response.json() setResults(data) } catch (error) { console.error('Search failed:', error) } }, 500, // Wait 500ms after last keystroke { leading: false, // Don't invoke on leading edge trailing: true, // Invoke on trailing edge maxWait: 2000 // Maximum wait time } ) const handleInputChange = (e) => { const value = e.target.value setSearchTerm(value) debouncedSearch(value) } const handleCancel = () => { debouncedSearch.cancel() // Cancel pending invocation setResults([]) } const handleSearchNow = () => { debouncedSearch.flush() // Immediately invoke pending call } const isPending = debouncedSearch.isPending() return ( <div> <input type="text" value={searchTerm} onChange={handleInputChange} placeholder="Search..." /> <button onClick={handleCancel}>Cancel</button> <button onClick={handleSearchNow}>Search Now</button> {isPending && <span>Searching...</span>} <ul> {results.map(result => ( <li key={result.id}>{result.title}</li> ))} </ul> </div> ) } ``` ## useInterval Execute callback functions at regular intervals with proper cleanup and dynamic delay control. ```typescript import { useState } from 'react' import { useInterval } from 'usehooks-ts' function Timer() { const [count, setCount] = useState(0) const [delay, setDelay] = useState(1000) const [isRunning, setIsRunning] = useState(true) // Pass null to stop the interval useInterval( () => { setCount(c => c + 1) }, isRunning ? delay : null ) return ( <div> <h1>Timer: {count}s</h1> <button onClick={() => setIsRunning(!isRunning)}> {isRunning ? 'Pause' : 'Resume'} </button> <button onClick={() => setCount(0)}>Reset</button> <div> <label> Interval (ms): <input type="number" value={delay} onChange={(e) => setDelay(Number(e.target.value))} /> </label> </div> </div> ) } function DataPolling() { const [data, setData] = useState(null) const [lastUpdate, setLastUpdate] = useState(new Date()) // Poll API every 5 seconds useInterval(async () => { try { const response = await fetch('/api/data') const json = await response.json() setData(json) setLastUpdate(new Date()) } catch (error) { console.error('Polling failed:', error) } }, 5000) return ( <div> <p>Last updated: {lastUpdate.toLocaleTimeString()}</p> <pre>{JSON.stringify(data, null, 2)}</pre> </div> ) } ``` ## useCopyToClipboard Copy text to clipboard using the Clipboard API with success/failure tracking. ```typescript import { useCopyToClipboard } from 'usehooks-ts' function CopyButton({ text }) { const [copiedText, copy] = useCopyToClipboard() const [showFeedback, setShowFeedback] = useState(false) const handleCopy = async () => { const success = await copy(text) if (success) { setShowFeedback(true) setTimeout(() => setShowFeedback(false), 2000) } else { alert('Failed to copy to clipboard') } } return ( <div> <button onClick={handleCopy}> {showFeedback ? 'ā Copied!' : 'Copy'} </button> {copiedText && <p>Last copied: {copiedText}</p>} </div> ) } function CodeBlock({ code }) { const [copiedText, copy] = useCopyToClipboard() const handleCopy = async () => { const success = await copy(code) console.log(success ? 'Code copied!' : 'Copy failed') } return ( <div className="code-block"> <pre><code>{code}</code></pre> <button onClick={handleCopy}> {copiedText === code ? 'Copied!' : 'Copy Code'} </button> </div> ) } ``` ## useMap Manage Map data structure state with React state management patterns. ```typescript import { useMap } from 'usehooks-ts' function UserCache() { const [users, { set, setAll, remove, reset }] = useMap( new Map([ [1, { id: 1, name: 'Alice' }], [2, { id: 2, name: 'Bob' }] ]) ) const addUser = (user) => { set(user.id, user) } const updateUser = (id, updates) => { const user = users.get(id) if (user) { set(id, { ...user, ...updates }) } } const deleteUser = (id) => { remove(id) } const bulkAddUsers = (userArray) => { const entries = userArray.map(user => [user.id, user]) setAll(new Map(entries)) } const clearCache = () => { reset() // Clears all entries } return ( <div> <h2>User Cache ({users.size} users)</h2> <ul> {Array.from(users.entries()).map(([id, user]) => ( <li key={id}> {user.name} <button onClick={() => updateUser(id, { name: user.name + '!' })}> Update </button> <button onClick={() => deleteUser(id)}>Delete</button> </li> ))} </ul> <button onClick={() => addUser({ id: Date.now(), name: 'New User' })}> Add User </button> <button onClick={clearCache}>Clear All</button> </div> ) } ``` ## useSessionStorage Persist state in sessionStorage (similar to useLocalStorage but session-scoped). ```typescript import { useSessionStorage } from 'usehooks-ts' function MultiStepForm() { const [formData, setFormData, removeFormData] = useSessionStorage('form-data', { step: 1, personal: {}, contact: {}, preferences: {} }) const updateStep = (step, data) => { setFormData(prev => ({ ...prev, step: step + 1, [step === 1 ? 'personal' : step === 2 ? 'contact' : 'preferences']: data })) } const resetForm = () => { removeFormData() // Clears sessionStorage and resets to initial value } return ( <div> <h2>Step {formData.step} of 3</h2> {formData.step === 1 && ( <PersonalInfoForm data={formData.personal} onNext={(data) => updateStep(1, data)} /> )} {formData.step === 2 && ( <ContactForm data={formData.contact} onNext={(data) => updateStep(2, data)} /> )} {formData.step === 3 && ( <PreferencesForm data={formData.preferences} onSubmit={(data) => { updateStep(3, data) // Submit complete form }} /> )} <button onClick={resetForm}>Start Over</button> </div> ) } ``` ## useWindowSize Track window dimensions with optional debouncing for performance optimization. ```typescript import { useWindowSize } from 'usehooks-ts' function ResponsiveComponent() { const { width, height } = useWindowSize({ initializeWithValue: true, debounceDelay: 150 // Debounce resize events }) const getBreakpoint = () => { if (width < 640) return 'mobile' if (width < 1024) return 'tablet' return 'desktop' } return ( <div> <h1>Window Size Tracker</h1> <p>Width: {width}px</p> <p>Height: {height}px</p> <p>Breakpoint: {getBreakpoint()}</p> <p>Aspect Ratio: {(width / height).toFixed(2)}</p> {width < 640 && <MobileView />} {width >= 640 && width < 1024 && <TabletView />} {width >= 1024 && <DesktopView />} </div> ) } ``` ## useHover Track hover state of DOM elements with ref-based detection. ```typescript import { useRef } from 'react' import { useHover } from 'usehooks-ts' function HoverCard() { const hoverRef = useRef(null) const isHover = useHover(hoverRef) return ( <div ref={hoverRef} style={{ padding: '20px', background: isHover ? '#3b82f6' : '#e5e7eb', color: isHover ? 'white' : 'black', transition: 'all 0.3s ease', transform: isHover ? 'scale(1.05)' : 'scale(1)' }} > <h3>{isHover ? 'Hovering! šÆ' : 'Hover over me'}</h3> <p>Hover state: {isHover ? 'true' : 'false'}</p> </div> ) } function Tooltip({ children, text }) { const tooltipRef = useRef(null) const isHover = useHover(tooltipRef) return ( <div ref={tooltipRef} style={{ position: 'relative', display: 'inline-block' }}> {children} {isHover && ( <div className="tooltip"> {text} </div> )} </div> ) } ``` ## useDebounceValue Debounce value changes (different from useDebounceCallback which debounces function calls). ```typescript import { useState } from 'react' import { useDebounceValue } from 'usehooks-ts' function LiveSearch() { const [searchTerm, setSearchTerm] = useState('') const [debouncedSearch] = useDebounceValue(searchTerm, 500) // This effect runs only when debounced value changes useEffect(() => { if (debouncedSearch) { fetch(`/api/search?q=${debouncedSearch}`) .then(res => res.json()) .then(data => console.log('Search results:', data)) } }, [debouncedSearch]) return ( <div> <input type="text" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} placeholder="Type to search..." /> <p>Actual value: {searchTerm}</p> <p>Debounced value: {debouncedSearch}</p> <p>API will be called with: {debouncedSearch || 'nothing yet'}</p> </div> ) } ``` ## useOnClickOutside Detect clicks outside of specified elements for dropdown/modal closing. ```typescript import { useRef } from 'react' import { useOnClickOutside } from 'usehooks-ts' function Dropdown() { const [isOpen, setIsOpen] = useState(false) const dropdownRef = useRef(null) useOnClickOutside(dropdownRef, () => { setIsOpen(false) }) return ( <div ref={dropdownRef}> <button onClick={() => setIsOpen(!isOpen)}> Toggle Menu {isOpen ? 'ā²' : 'ā¼'} </button> {isOpen && ( <div className="dropdown-menu"> <a href="/profile">Profile</a> <a href="/settings">Settings</a> <a href="/logout">Logout</a> </div> )} </div> ) } function Modal({ isOpen, onClose, children }) { const modalRef = useRef(null) useOnClickOutside(modalRef, onClose) if (!isOpen) return null return ( <div className="modal-overlay"> <div ref={modalRef} className="modal-content"> {children} <button onClick={onClose}>Close</button> </div> </div> ) } ``` ## useStep Manage multi-step process navigation with boundary checks and helpers. ```typescript import { useStep } from 'usehooks-ts' function Wizard() { const [currentStep, { goToNextStep, goToPrevStep, reset, canGoToNextStep, canGoToPrevStep, setStep }] = useStep(4) const steps = [ { id: 1, title: 'Personal Info', component: PersonalInfo }, { id: 2, title: 'Address', component: Address }, { id: 3, title: 'Payment', component: Payment }, { id: 4, title: 'Review', component: Review } ] const CurrentStepComponent = steps[currentStep - 1].component return ( <div> <div className="progress-bar"> {steps.map((step) => ( <div key={step.id} className={currentStep === step.id ? 'active' : ''} onClick={() => setStep(step.id)} > {step.title} </div> ))} </div> <CurrentStepComponent /> <div className="navigation"> <button onClick={goToPrevStep} disabled={!canGoToPrevStep} > Previous </button> <button onClick={reset}>Start Over</button> <button onClick={goToNextStep} disabled={!canGoToNextStep} > {currentStep === steps.length ? 'Submit' : 'Next'} </button> </div> <p>Step {currentStep} of {steps.length}</p> </div> ) } ``` ## useScript Dynamically load external scripts with status tracking. ```typescript import { useScript } from 'usehooks-ts' function GoogleMapsComponent() { const status = useScript( `https://maps.googleapis.com/maps/api/js?key=${API_KEY}`, { removeOnUnmount: false // Keep script loaded after unmount } ) useEffect(() => { if (status === 'ready') { // Initialize map const map = new google.maps.Map(document.getElementById('map'), { center: { lat: -34.397, lng: 150.644 }, zoom: 8 }) } }, [status]) return ( <div> {status === 'loading' && <p>Loading Google Maps...</p>} {status === 'error' && <p>Failed to load Google Maps</p>} {status === 'ready' && <div id="map" style={{ height: '400px' }} />} </div> ) } function StripePayment() { const stripeStatus = useScript('https://js.stripe.com/v3/') const [stripe, setStripe] = useState(null) useEffect(() => { if (stripeStatus === 'ready' && window.Stripe) { setStripe(window.Stripe(PUBLISHABLE_KEY)) } }, [stripeStatus]) return stripe ? <CheckoutForm stripe={stripe} /> : <Loading /> } ``` ## useIsClient Detect client-side rendering for SSR-safe operations. ```typescript import { useIsClient } from 'usehooks-ts' function ClientOnlyComponent() { const isClient = useIsClient() if (!isClient) { return <div>Server-side render placeholder</div> } // Safe to use window, document, localStorage, etc. return ( <div> <p>Window width: {window.innerWidth}px</p> <p>User agent: {navigator.userAgent}</p> <p>Local storage available: {typeof localStorage !== 'undefined' ? 'Yes' : 'No'}</p> </div> ) } function ConditionalRender() { const isClient = useIsClient() return ( <div> <h1>My App</h1> {isClient && <BrowserOnlyFeature />} {isClient ? <ClientView /> : <ServerView />} </div> ) } ``` ## Complete Application Example ```typescript import { useLocalStorage, useMediaQuery, useDarkMode, useDebounceCallback, useIntersectionObserver, useOnClickOutside } from 'usehooks-ts' function TodoApp() { // State management with persistence const [todos, setTodos] = useLocalStorage('todos', []) const [searchTerm, setSearchTerm] = useState('') // Theme management const { isDarkMode, toggle } = useDarkMode() const isMobile = useMediaQuery('(max-width: 768px)') // Debounced search const debouncedSearch = useDebounceCallback( (term) => { console.log('Searching for:', term) // Perform search logic }, 300 ) // Lazy loading for todo items const { ref: lastItemRef, isIntersecting } = useIntersectionObserver({ threshold: 0.1, rootMargin: '100px' }) // Dropdown menu const [isMenuOpen, setIsMenuOpen] = useState(false) const menuRef = useRef(null) useOnClickOutside(menuRef, () => setIsMenuOpen(false)) // Load more todos when scrolling useEffect(() => { if (isIntersecting) { loadMoreTodos() } }, [isIntersecting]) const addTodo = (text) => { setTodos([...todos, { id: Date.now(), text, completed: false, createdAt: new Date().toISOString() }]) } const toggleTodo = (id) => { setTodos(todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo )) } const deleteTodo = (id) => { setTodos(todos.filter(todo => todo.id !== id)) } const handleSearchChange = (e) => { const value = e.target.value setSearchTerm(value) debouncedSearch(value) } const filteredTodos = todos.filter(todo => todo.text.toLowerCase().includes(searchTerm.toLowerCase()) ) return ( <div className={isDarkMode ? 'dark' : 'light'}> <header> <h1>Todo App</h1> <button onClick={toggle}> {isDarkMode ? 'š' : 'āļø'} </button> <div ref={menuRef}> <button onClick={() => setIsMenuOpen(!isMenuOpen)}> Menu </button> {isMenuOpen && ( <div className="dropdown"> <a href="/settings">Settings</a> <a href="/export">Export</a> </div> )} </div> </header> <main> <input type="text" value={searchTerm} onChange={handleSearchChange} placeholder="Search todos..." /> <TodoForm onSubmit={addTodo} /> <ul className={isMobile ? 'mobile-list' : 'desktop-list'}> {filteredTodos.map((todo, index) => ( <li key={todo.id} ref={index === filteredTodos.length - 1 ? lastItemRef : null} > <input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(todo.id)} /> <span className={todo.completed ? 'completed' : ''}> {todo.text} </span> <button onClick={() => deleteTodo(todo.id)}>Delete</button> </li> ))} </ul> {isIntersecting && <div>Loading more...</div>} </main> <footer> <p>{todos.length} total todos</p> <p>{todos.filter(t => t.completed).length} completed</p> </footer> </div> ) } ``` ## Summary usehooks-ts provides a comprehensive suite of production-ready React hooks that cover the most common development patterns: state management (useBoolean, useCounter, useToggle, useStep), data persistence (useLocalStorage, useSessionStorage), timing operations (useInterval, useTimeout, useDebounceCallback), event handling (useEventListener, useClickAnyWhere, useOnClickOutside), DOM observation (useIntersectionObserver, useResizeObserver, useHover), responsive design (useMediaQuery, useWindowSize), theming (useDarkMode, useTernaryDarkMode), and browser APIs (useCopyToClipboard, useScript, useScreen). Each hook is designed with TypeScript type safety, SSR compatibility, and proper cleanup handling, making them suitable for modern React applications including Next.js, Remix, and other SSR frameworks. The library excels at composability, allowing hooks to work together seamlessly (as seen with useDarkMode combining useMediaQuery, useLocalStorage, and useIsomorphicLayoutEffect). With features like cross-tab synchronization for storage hooks, custom serialization options, debounce/throttle controls, and intersection observer support, usehooks-ts eliminates boilerplate code while maintaining flexibility. The tree-shakable ESM build ensures minimal bundle impact, while comprehensive TypeScript definitions provide excellent developer experience. Whether building responsive layouts, implementing dark mode, managing form state, lazy loading content, or handling complex user interactions, usehooks-ts provides battle-tested solutions that follow React best practices and integrate smoothly into any React codebase.