React Hooks Shadcn Registry
Professional React hooks registry with 40+ copy-paste custom hooks for JavaScript, TypeScript, and Next.js. Install individual hooks directly into your project via shadcn/ui CLI. Full source code ownership, complete customization, and production-ready implementations.
Professional React Hooks Registry for Modern JavaScript Development
React hooks are essential building blocks for modern JavaScript and TypeScript applications. Our shadcn/ui registry delivers production-ready hooks directly into your codebase with complete source control and customization freedom.
Core Registry Benefits
- Complete Source Ownership: Hooks install as source code - no external dependencies or package management
- TypeScript-First: Comprehensive type definitions for React 18+ and Next.js applications
- Production-Optimized: Memory management, cleanup patterns, and performance best practices built-in
- Framework Compatible: Works with Next.js, Vite, Create React App, and modern tooling
- SSR Ready: Server-side rendering support with proper hydration handling
Hook Categories
- State Management: localStorage, sessionStorage, boolean states, counters, toggles, maps
- UI Interactions: click detection, hover effects, mouse tracking, scroll control
- Browser APIs: media queries, window sizing, dark mode, clipboard operations
- Performance: debouncing, throttling, intervals, timeouts, memory leak prevention
React State Management Hooks - localStorage, sessionStorage & More
Essential React hooks for state management in JavaScript and TypeScript applications. Handle localStorage persistence, sessionStorage, boolean states, counters, and complex state logic with these production-ready hooks.
useLocalStorage
"use client"import { useLocalStorage } from "@/components/ui/shadcn-io/use-local-storage"export function UseLocalStorageDemo() { const [value, setValue, removeValue] = useLocalStorage("test-key", 0) return ( <div className="space-y-4 rounded-lg border p-6"> <div> <p className="text-lg font-medium"> Count: <span className="font-mono">{value}</span> </p> <p className="text-sm text-muted-foreground"> This value persists across page reloads </p> </div> <div className="flex gap-2"> <button onClick={() => { setValue((x: number) => x + 1) }} className="rounded bg-primary px-3 py-2 text-sm text-primary-foreground hover:bg-primary/90" > Increment </button> <button onClick={() => { setValue((x: number) => x - 1) }} className="rounded border border-input bg-background px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground" > Decrement </button> <button onClick={() => { removeValue() }} className="rounded border border-destructive px-3 py-2 text-sm text-destructive hover:bg-destructive hover:text-destructive-foreground" > Reset </button> </div> <div className="text-xs text-muted-foreground"> <p>Try refreshing the page to see the value persist!</p> <p className="font-mono">localStorage key: "test-key"</p> </div> </div> )}export default UseLocalStorageDemo
"use client"import { useCallback, useEffect, useState } from "react"import type { Dispatch, SetStateAction } from "react"import { useEventCallback } from "@/components/ui/kibo-ui/use-event-callback"import { useEventListener } from "@/components/ui/kibo-ui/use-event-listener"declare global { interface WindowEventMap { "local-storage": CustomEvent }}type UseLocalStorageOptions<T> = { serializer?: (value: T) => string deserializer?: (value: string) => T initializeWithValue?: boolean}const IS_SERVER = typeof window === "undefined"export function useLocalStorage<T>( key: string, initialValue: T | (() => T), options: UseLocalStorageOptions<T> = {},): [T, Dispatch<SetStateAction<T>>, () => void] { const { initializeWithValue = true } = options const serializer = useCallback<(value: T) => string>( (value) => { if (options.serializer) { return options.serializer(value) } return JSON.stringify(value) }, [options], ) const deserializer = useCallback<(value: string) => T>( (value) => { if (options.deserializer) { return options.deserializer(value) } // Support 'undefined' as a value if (value === "undefined") { return undefined as unknown as T } const defaultValue = initialValue instanceof Function ? initialValue() : initialValue let parsed: unknown try { parsed = JSON.parse(value) } catch (error) { console.error("Error parsing JSON:", error) return defaultValue // Return initialValue if parsing fails } return parsed as T }, [options, initialValue], ) // Get from local storage then // parse stored json or return initialValue const readValue = useCallback((): T => { const initialValueToUse = initialValue instanceof Function ? initialValue() : initialValue // Prevent build error "window is undefined" but keep working if (IS_SERVER) { return initialValueToUse } try { const raw = window.localStorage.getItem(key) return raw ? deserializer(raw) : initialValueToUse } catch (error) { console.warn(`Error reading localStorage key "${key}":`, error) return initialValueToUse } }, [initialValue, key, deserializer]) const [storedValue, setStoredValue] = useState(() => { if (initializeWithValue) { return readValue() } return initialValue instanceof Function ? initialValue() : initialValue }) // Return a wrapped version of useState's setter function that ... // ... persists the new value to localStorage. const setValue: Dispatch<SetStateAction<T>> = useEventCallback((value) => { // Prevent build error "window is undefined" but keeps working if (IS_SERVER) { console.warn( `Tried setting localStorage key "${key}" even though environment is not a client`, ) } try { // Allow value to be a function so we have the same API as useState const newValue = value instanceof Function ? value(readValue()) : value // Save to local storage window.localStorage.setItem(key, serializer(newValue)) // Save state setStoredValue(newValue) // We dispatch a custom event so every similar useLocalStorage hook is notified window.dispatchEvent(new StorageEvent("local-storage", { key })) } catch (error) { console.warn(`Error setting localStorage key "${key}":`, error) } }) const removeValue = useEventCallback(() => { // Prevent build error "window is undefined" but keeps working if (IS_SERVER) { console.warn( `Tried removing localStorage key "${key}" even though environment is not a client`, ) } const defaultValue = initialValue instanceof Function ? initialValue() : initialValue // Remove the key from local storage window.localStorage.removeItem(key) // Save state with default value setStoredValue(defaultValue) // We dispatch a custom event so every similar useLocalStorage hook is notified window.dispatchEvent(new StorageEvent("local-storage", { key })) }) useEffect(() => { setStoredValue(readValue()) // eslint-disable-next-line react-hooks/exhaustive-deps }, [key]) const handleStorageChange = useCallback( (event: StorageEvent | CustomEvent) => { if ((event as StorageEvent).key && (event as StorageEvent).key !== key) { return } setStoredValue(readValue()) }, [key, readValue], ) // this only works for other documents, not the current one useEventListener("storage", handleStorageChange) // this is a custom event, triggered in writeValueToLocalStorage // See: useLocalStorage() useEventListener("local-storage", handleStorageChange) return [storedValue, setValue, removeValue]}export type { UseLocalStorageOptions }
Persist state across browser sessions with automatic serialization and cross-tab synchronization. Perfect for user preferences, authentication tokens, and application settings.
useSessionStorage
"use client";import { useSessionStorage } from '@/components/ui/shadcn-io/use-session-storage';export default function UseSessionStorageDemo() { const [count, setCount, removeCount] = useSessionStorage('demo-count', 0); return ( <div className="space-y-3 p-4 max-w-sm mx-auto"> <div className="space-y-1"> <h3 className="font-semibold">Session Storage Counter</h3> <p className="text-xs text-muted-foreground"> Persists across page reloads (same tab only) </p> </div> <div className="p-4 bg-muted rounded-lg text-center"> <div className="text-3xl font-bold text-blue-600 mb-1"> {count} </div> <div className="text-xs text-muted-foreground"> Current Count </div> </div> <div className="grid grid-cols-3 gap-1"> <button onClick={() => setCount(x => x - 1)} className="px-3 py-2 bg-red-500 text-white rounded text-sm hover:bg-red-600 transition-colors" > −1 </button> <button onClick={() => setCount(x => x + 1)} className="px-3 py-2 bg-green-500 text-white rounded text-sm hover:bg-green-600 transition-colors" > +1 </button> <button onClick={removeCount} className="px-3 py-2 bg-gray-500 text-white rounded text-sm hover:bg-gray-600 transition-colors" > Reset </button> </div> <div className="grid grid-cols-2 gap-1"> <button onClick={() => setCount(x => x + 5)} className="px-3 py-1.5 bg-blue-500 text-white rounded text-xs hover:bg-blue-600 transition-colors" > +5 </button> <button onClick={() => setCount(x => x - 5)} className="px-3 py-1.5 bg-purple-500 text-white rounded text-xs hover:bg-purple-600 transition-colors" > −5 </button> </div> <div className="text-xs text-muted-foreground text-center"> 💡 Try refreshing the page - the value persists! </div> </div> );}
Temporary state persistence within the same browser tab. Ideal for form data, multi-step wizards, and draft content that shouldn't persist across sessions.
useBoolean
"use client";import { useBoolean } from '@/components/ui/shadcn-io/use-boolean';export default function UseBooleanDemo() { const { value, setTrue, setFalse, toggle } = useBoolean(false); return ( <div className="flex flex-col items-center justify-center min-h-[300px] p-8 space-y-6"> {/* Status Display */} <div className="text-center space-y-2"> <h3 className="text-lg font-semibold">Boolean State</h3> <div className={`px-6 py-3 rounded-lg text-lg font-medium ${ value ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300" : "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300" }`}> {value ? 'True' : 'False'} </div> </div> {/* Control Buttons */} <div className="flex flex-wrap gap-3 justify-center"> <button onClick={setTrue} className="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors disabled:opacity-50" disabled={value} > Set True </button> <button onClick={setFalse} className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors disabled:opacity-50" disabled={!value} > Set False </button> <button onClick={toggle} className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors" > Toggle </button> </div> {/* Usage Counter */} <div className="text-center text-sm text-muted-foreground"> Current value: <code className="bg-muted px-1 py-0.5 rounded">{String(value)}</code> </div> </div> );}
Enhanced boolean state management with convenient toggle and setter methods. Essential for modals, dropdowns, loading states, and feature flags.
useCounter
"use client";import * as React from "react";import { Button } from "@/components/ui/button";import { useCounter } from "@/components/ui/shadcn-io/use-counter";export default function UseCounterDemo() { const { count, increment, decrement, reset, setCount } = useCounter(0); const multiplyBy2 = () => { setCount((x: number) => x * 2); }; return ( <div className="flex flex-col items-center gap-4"> <div className="text-4xl font-bold text-center"> {count} </div> <div className="flex flex-wrap justify-center gap-2"> <Button onClick={increment}> Increment </Button> <Button onClick={decrement} variant="outline"> Decrement </Button> <Button onClick={reset} variant="secondary"> Reset </Button> <Button onClick={multiplyBy2} variant="destructive"> Multiply by 2 </Button> </div> <p className="text-center text-muted-foreground max-w-md"> A simple counter with increment, decrement, reset, and custom operations. </p> </div> );}
import * as React from "react"import { Slot } from "@radix-ui/react-slot"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "@/lib/utils"const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, })function Button({ className, variant, size, asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> )}export { Button, buttonVariants }
Robust counter implementation with boundaries, step control, and reset functionality. Perfect for pagination, quantity selectors, and numeric inputs.
useToggle
"use client";import { useToggle } from '@/components/ui/shadcn-io/use-toggle';export default function UseToggleDemo() { const [value, toggle, setValue] = useToggle(false); // Custom toggle example const customToggle = () => { setValue((x: boolean) => !x); }; return ( <div className="flex flex-col items-center justify-center min-h-[300px] p-8 space-y-6"> {/* Status Display */} <div className="text-center space-y-2"> <h3 className="text-lg font-semibold">Toggle State</h3> <div className="text-center"> <p className="text-sm text-muted-foreground mb-2">Current Value:</p> <code className="px-3 py-2 bg-muted rounded-md text-lg font-mono"> {value.toString()} </code> </div> </div> {/* Control Buttons */} <div className="grid grid-cols-2 gap-3 w-full max-w-md"> <button onClick={() => setValue(true)} className="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors disabled:opacity-50" disabled={value} > Set True </button> <button onClick={() => setValue(false)} className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors disabled:opacity-50" disabled={!value} > Set False </button> <button onClick={toggle} className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors col-span-2" > Toggle </button> <button onClick={customToggle} className="px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700 transition-colors col-span-2" > Custom Toggle </button> </div> {/* Usage Info */} <div className="text-center text-sm text-muted-foreground max-w-md"> <p> This demonstrates the <code className="bg-muted px-1 py-0.5 rounded">useToggle</code> hook with array destructuring pattern similar to <code className="bg-muted px-1 py-0.5 rounded">useState</code>. </p> </div> </div> );}
Toggle between multiple values with type safety. Excellent for theme switching, view modes, and multi-state toggles.
useStep
"use client";import { useStep } from '@/components/ui/shadcn-io/use-step';export default function UseStepDemo() { const [currentStep, { canGoToPrevStep, canGoToNextStep, goToNextStep, goToPrevStep, reset, setStep, }] = useStep(5); return ( <div className="flex flex-col items-center justify-center min-h-[300px] p-8 space-y-6"> {/* Current Step Display */} <div className="text-center space-y-2"> <h3 className="text-lg font-semibold">Current Step</h3> <div className="text-4xl font-bold text-blue-600 dark:text-blue-400"> {currentStep} </div> <div className="text-sm text-muted-foreground">Step {currentStep} of 5</div> </div> {/* Progress Bar */} <div className="w-full max-w-xs"> <div className="flex space-x-1"> {Array.from({ length: 5 }, (_, i) => i + 1).map((step) => ( <div key={step} className={`flex-1 h-2 rounded-full ${ step <= currentStep ? "bg-blue-600" : "bg-gray-200 dark:bg-gray-700" }`} /> ))} </div> </div> {/* Navigation Controls */} <div className="flex gap-2 justify-center"> <button onClick={goToPrevStep} className="px-3 py-1.5 text-sm bg-gray-600 text-white rounded-md hover:bg-gray-700 transition-colors disabled:opacity-50" disabled={!canGoToPrevStep} > Previous </button> <button onClick={goToNextStep} className="px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors disabled:opacity-50" disabled={!canGoToNextStep} > Next </button> <button onClick={reset} className="px-3 py-1.5 text-sm bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors" > Reset </button> <button onClick={() => setStep(3)} className="px-3 py-1.5 text-sm bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors" > Set to step 3 </button> </div> {/* Status Information */} <div className="text-center text-sm text-muted-foreground space-y-1"> <div>Can go to previous step: <code className="bg-muted px-1 py-0.5 rounded">{canGoToPrevStep ? 'yes' : 'no'}</code></div> <div>Can go to next step: <code className="bg-muted px-1 py-0.5 rounded">{canGoToNextStep ? 'yes' : 'no'}</code></div> </div> </div> );}
Navigate through multi-step processes with validation and boundary handling. Essential for wizards, onboarding flows, and progress tracking.
useMap
"use client";import { Fragment } from 'react';import { useMap } from '@/components/ui/shadcn-io/use-map';export default function UseMapDemo() { const [map, actions] = useMap<string, string>([['welcome', '👋'], ['initial', '🆕']]); const addItem = () => { const timestamp = Date.now().toString(); actions.set(timestamp, '📦'); }; const setPreset = () => { actions.setAll([ ['hello', '👋'], ['data', '📊'], ['rocket', '🚀'], ]); }; const removeHello = () => { actions.remove('hello'); }; const resetMap = () => { actions.reset(); }; return ( <div className="flex flex-col items-center justify-center min-h-[300px] p-8 space-y-6"> {/* Status Display */} <div className="text-center space-y-2"> <h3 className="text-lg font-semibold">Map State Management</h3> <div className="px-4 py-2 rounded-lg bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300"> <div className="font-medium"> Size: {map.size} {map.size === 1 ? 'item' : 'items'} </div> </div> </div> {/* Map Display */} <div className="w-full max-w-md"> <div className="bg-muted rounded-lg p-4"> <div className="text-sm font-medium mb-3 text-center">Map Contents</div> {map.size === 0 ? ( <div className="text-sm text-muted-foreground text-center py-4"> Map is empty </div> ) : ( <div className="space-y-2"> {Array.from(map.entries()).map(([key, value]) => ( <div key={key} className="flex justify-between items-center text-sm p-2 bg-background rounded border"> <code className="text-xs font-mono">{key}</code> <span>{value}</span> </div> ))} </div> )} </div> </div> {/* Action Buttons */} <div className="grid grid-cols-2 gap-3 w-full max-w-md"> <button onClick={addItem} className="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors text-sm" > Add Item </button> <button onClick={setPreset} className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors text-sm" > Set Preset </button> <button onClick={removeHello} disabled={!map.has('hello')} className="px-4 py-2 bg-orange-600 text-white rounded-md hover:bg-orange-700 transition-colors disabled:opacity-50 text-sm" > Remove "hello" </button> <button onClick={resetMap} className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors text-sm" > Reset Map </button> </div> {/* Info */} <div className="text-center text-sm text-muted-foreground max-w-md"> <p>Manage Map state with type-safe operations</p> <p className="mt-1">Keys are unique and values can be any type</p> </div> </div> );}
Reactive Map state management with immutable updates and comprehensive API. Perfect for key-value stores, caches, and dynamic collections.
React UI Interaction Hooks - Click Events, Hover Effects & Mouse Tracking
Advanced React hooks for UI interactions and event handling in JavaScript applications. Implement click outside detection, hover effects, mouse position tracking, and scroll management with these TypeScript-ready hooks.
useOnClickOutside
"use client";import React, { useRef, useState } from 'react';import { useOnClickOutside } from '@/components/ui/shadcn-io/use-on-click-outside';export default function UseOnClickOutsideDemo() { const [isOpen, setIsOpen] = useState(false); const [clickCount, setClickCount] = useState({ inside: 0, outside: 0 }); const targetRef = useRef<HTMLDivElement>(null); const handleClickOutside = () => { setClickCount(prev => ({ ...prev, outside: prev.outside + 1 })); if (isOpen) { setIsOpen(false); } }; const handleClickInside = () => { setClickCount(prev => ({ ...prev, inside: prev.inside + 1 })); if (!isOpen) { setIsOpen(true); } }; useOnClickOutside(targetRef as React.RefObject<HTMLElement>, handleClickOutside); return ( <div className="flex flex-col items-center justify-center min-h-[300px] p-8 space-y-6"> {/* Status Display */} <div className="text-center space-y-2"> <h3 className="text-lg font-semibold">Click Outside Detection</h3> <div className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${ isOpen ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300" : "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300" }`}> Target: {isOpen ? '🟢 Active' : '🔴 Inactive'} </div> </div> {/* Interactive Target */} <div ref={targetRef} onClick={handleClickInside} className={` w-48 h-32 rounded-lg cursor-pointer transition-all duration-200 border-2 border-dashed flex items-center justify-center text-center ${isOpen ? 'border-green-400 bg-green-50 dark:bg-green-900/20 scale-105' : 'border-gray-300 bg-gray-50 dark:bg-gray-800 hover:border-gray-400' } `} > <div> <div className="text-2xl mb-2">{isOpen ? '🎯' : '📍'}</div> <div className="text-sm font-medium"> {isOpen ? 'Click outside to close' : 'Click me to activate'} </div> <div className="text-xs text-muted-foreground mt-1"> Target Element </div> </div> </div> {/* Click Statistics */} <div className="grid grid-cols-2 gap-4 text-center"> <div className="px-4 py-2 bg-muted rounded-lg"> <div className="text-lg font-bold text-green-600"> {clickCount.inside} </div> <div className="text-xs text-muted-foreground">Inside Clicks</div> </div> <div className="px-4 py-2 bg-muted rounded-lg"> <div className="text-lg font-bold text-blue-600"> {clickCount.outside} </div> <div className="text-xs text-muted-foreground">Outside Clicks</div> </div> </div> {/* Instructions */} <div className="text-center text-sm text-muted-foreground max-w-md"> <p>Click the target element to activate it, then click anywhere outside to deactivate.</p> <p className="mt-1">The hook detects clicks outside the specified element.</p> </div> </div> );}
Detect clicks outside elements with support for multiple refs and event types. Essential for modals, dropdowns, context menus, and popup components.
useHover
"use client";import React, { useRef } from 'react';import { useHover } from '@/components/ui/shadcn-io/use-hover';export default function UseHoverDemo() { const hoverRef = useRef<HTMLDivElement>(null); const isHover = useHover(hoverRef as React.RefObject<HTMLElement>); return ( <div className="flex flex-col items-center justify-center min-h-[300px] p-8 space-y-6"> {/* Status Display */} <div className="text-center space-y-2"> <h3 className="text-lg font-semibold">Hover Detection</h3> <div className={`px-6 py-3 rounded-lg text-lg font-medium transition-colors ${ isHover ? "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300" : "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300" }`}> {isHover ? '🎯 Hovering' : '👋 Not Hovering'} </div> </div> {/* Hover Target */} <div ref={hoverRef} className={` w-48 h-32 border-2 border-dashed rounded-lg flex items-center justify-center text-center cursor-pointer transition-all duration-200 ${isHover ? 'border-blue-400 bg-blue-50 dark:bg-blue-900/20 scale-105' : 'border-gray-300 bg-gray-50 dark:bg-gray-800 hover:border-gray-400' } `} > <div> <div className="text-2xl mb-2">{isHover ? '😊' : '😴'}</div> <div className="text-sm font-medium"> {isHover ? 'You\'re hovering!' : 'Hover over me'} </div> </div> </div> {/* State Info */} <div className="text-center space-y-2"> <div className="text-sm text-muted-foreground"> Hook returns: <code className="bg-muted px-2 py-1 rounded text-xs"> {isHover ? 'true' : 'false'} </code> </div> <div className="text-xs text-muted-foreground max-w-md"> Move your cursor over the dashed area to see the hover state change </div> </div> </div> );}
Track hover state with customizable delay and touch support. Perfect for tooltips, interactive cards, and hover effects.
useClickAnywhere
"use client";import { useClickAnyWhere } from '@/components/ui/shadcn-io/use-click-anywhere';import { useState } from 'react';export default function UseClickAnywhereDemo() { const [clickCount, setClickCount] = useState(0); const [lastClick, setLastClick] = useState<{ x: number; y: number } | null>(null); useClickAnyWhere((event) => { setClickCount(prev => prev + 1); setLastClick({ x: event.clientX, y: event.clientY }); }); const reset = () => { // Reset after a tiny delay to avoid counting this click setTimeout(() => { setClickCount(0); setLastClick(null); }, 0); }; return ( <div className="flex flex-col items-center justify-center min-h-[300px] p-8 space-y-6"> {/* Status Display */} <div className="text-center space-y-2"> <h3 className="text-lg font-semibold">Click Detection</h3> <div className="px-6 py-3 rounded-lg bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300"> <div className="text-2xl font-bold">{clickCount}</div> <div className="text-sm">Total Clicks</div> </div> </div> {/* Click Position */} {lastClick && ( <div className="text-center space-y-2"> <div className="text-sm text-muted-foreground"> Last click position </div> <code className="bg-muted px-3 py-1.5 rounded text-sm"> x: {lastClick.x}, y: {lastClick.y} </code> </div> )} {/* Instructions */} <div className="text-center text-sm text-muted-foreground max-w-md"> Click anywhere on the page to see the hook in action. Every click is detected and counted. </div> {/* Reset Button */} <button onClick={reset} className="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 transition-colors" > Reset Counter </button> </div> );}
Global click tracking with position data and event filtering. Useful for analytics, user behavior tracking, and interactive experiences.
useMousePosition
"use client"import * as React from "react"import { cn } from "@/lib/utils"import { useMousePosition } from "@/components/ui/shadcn-io/use-mouse-position"export function UseMousePositionDemo() { const containerRef = React.useRef<HTMLDivElement>(null) const [mouse, trackingRef] = useMousePosition<HTMLDivElement>() // Only show tracking when mouse is within the container const xIntersecting = mouse.elementX !== undefined && mouse.elementX >= 0 && mouse.elementX <= 320 const yIntersecting = mouse.elementY !== undefined && mouse.elementY >= 0 && mouse.elementY <= 200 const isIntersecting = xIntersecting && yIntersecting return ( <div className="space-y-3 p-4 max-w-sm mx-auto"> <div className="space-y-1"> <h3 className="font-semibold">Mouse Position Tracker</h3> <p className="text-xs text-muted-foreground"> Hover over the area below to see coordinates </p> </div> <div ref={containerRef} className="relative w-full h-48 overflow-hidden rounded-lg border-2 border-dashed border-primary/30 bg-gradient-to-br from-blue-50 to-purple-50 dark:from-blue-950 dark:to-purple-950" > <div ref={trackingRef} className={cn( "absolute inset-0 transition-all duration-200", isIntersecting ? "bg-primary/5" : "bg-transparent" )} > <div className="absolute inset-0 flex items-center justify-center"> <div className="text-center"> <div className="text-2xl mb-2">🎯</div> <div className="text-sm font-medium text-muted-foreground"> Mouse Tracking Area </div> <div className="text-xs text-muted-foreground mt-1"> Move your cursor here </div> </div> </div> {/* Show position indicator only when intersecting */} {isIntersecting && mouse.elementX !== undefined && mouse.elementY !== undefined && ( <div className="absolute w-3 h-3 bg-primary rounded-full shadow-lg transform -translate-x-1/2 -translate-y-1/2 pointer-events-none" style={{ left: `${mouse.elementX}px`, top: `${mouse.elementY}px`, }} /> )} </div> </div> {/* Position display */} <div className="grid grid-cols-2 gap-2 text-xs"> <div className="p-2 bg-muted rounded"> <div className="font-medium">Global Position</div> <div className="text-blue-600 font-mono"> {mouse.x}, {mouse.y} </div> </div> <div className="p-2 bg-muted rounded"> <div className="font-medium">Relative Position</div> <div className="text-purple-600 font-mono"> {isIntersecting && mouse.elementX !== undefined && mouse.elementY !== undefined ? `${Math.round(mouse.elementX)}, ${Math.round(mouse.elementY)}` : '-, -' } </div> </div> </div> <div className="text-xs text-muted-foreground text-center"> {isIntersecting ? "🎯 Mouse detected in tracking area" : "Move mouse over the tracking area above" } </div> </div> )}export default UseMousePositionDemo
"use client"import * as React from "react"export type Position = { x: number y: number elementX?: number elementY?: number elementPositionX?: number elementPositionY?: number}export function useMousePosition<T extends HTMLElement>(): [ Position, React.Ref<T>,] { const [state, setState] = React.useState<Position>({ x: 0, y: 0, }) const ref = React.useRef<T>(null) React.useLayoutEffect(() => { const handleMouseMove = (event: MouseEvent) => { const newState: Position = { x: event.pageX, y: event.pageY, } if (ref.current?.nodeType === Node.ELEMENT_NODE) { const { left, top } = ref.current.getBoundingClientRect() const elementPositionX = left + window.scrollX const elementPositionY = top + window.scrollY const elementX = event.pageX - elementPositionX const elementY = event.pageY - elementPositionY newState.elementPositionX = elementPositionX newState.elementPositionY = elementPositionY newState.elementX = elementX newState.elementY = elementY } setState((s) => ({ ...s, ...newState, })) } document.addEventListener("mousemove", handleMouseMove) return () => document.removeEventListener("mousemove", handleMouseMove) }, []) return [state, ref]}
Real-time mouse position tracking with throttling and boundary detection. Ideal for custom cursors, drag operations, and interactive visualizations.
useScrollLock
"use client"import { useState, useRef } from "react"import { Button } from "@/components/ui/button"import { Badge } from "@/components/ui/badge"import { Label } from "@/components/ui/label"import { useScrollLock } from "@/components/ui/shadcn-io/use-scroll-lock"export function UseScrollLockDemo() { const [showModal, setShowModal] = useState(false) const scrollRef = useRef<HTMLDivElement>(null) const { isLocked, lock, unlock } = useScrollLock({ autoLock: false, lockTarget: "#scrollable-content", }) return ( <div className="space-y-3 p-4 max-w-sm mx-auto"> <div className="space-y-2"> <div className="flex items-center justify-between"> <Label className="text-sm font-medium">Scroll Lock Control</Label> <Badge variant={isLocked ? "destructive" : "secondary"} className="text-xs" > {isLocked ? "🔒 Locked" : "🔓 Unlocked"} </Badge> </div> </div> <div className="space-y-2"> <Label className="text-sm font-medium">Scrollable Content Area</Label> <div id="scrollable-content" ref={scrollRef} className="h-64 overflow-y-auto border-2 rounded-lg p-3 bg-background" > <div className="space-y-3"> {Array.from({ length: 25 }, (_, i) => ( <div key={i} className="p-3 rounded-lg bg-white dark:bg-gray-900 border shadow-sm"> <div className="flex items-center gap-2 mb-2"> <span className="text-lg">📄</span> <span className="font-medium text-sm">Article {i + 1}</span> <Badge variant="outline" className="text-xs ml-auto"> {isLocked ? "🚫" : "📜"} </Badge> </div> <div className="text-xs text-muted-foreground"> {isLocked ? "Scroll functionality is currently disabled by the lock mechanism." : "This content area is scrollable. Try the lock button to disable scrolling." } </div> <div className="text-xs text-muted-foreground mt-2 leading-relaxed"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam. </div> </div> ))} </div> </div> <div className="text-xs text-center text-muted-foreground"> {isLocked ? "⚠️ Scrolling is locked - content is not scrollable" : "💡 Try scrolling above, then click 'Lock' to disable it" } </div> </div> <div className="grid grid-cols-3 gap-1"> <Button onClick={lock} disabled={isLocked} variant="destructive" size="sm" className="h-8 text-xs" > Lock </Button> <Button onClick={unlock} disabled={!isLocked} variant="default" size="sm" className="h-8 text-xs bg-green-600 hover:bg-green-700" > Unlock </Button> <Button onClick={() => setShowModal(true)} variant="outline" size="sm" className="h-8 text-xs" > Modal </Button> </div> <div className="text-xs text-muted-foreground text-center"> Lock prevents scrolling in the target area </div> {showModal && <Modal onClose={() => setShowModal(false)} />} </div> )}function Modal({ onClose }: { onClose: () => void }) { useScrollLock() return ( <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> <div className="bg-background border rounded-lg p-4 max-w-sm w-full shadow-lg"> <div className="flex items-center justify-between mb-3"> <h2 className="font-semibold">Body Scroll Lock</h2> <Badge variant="destructive" className="text-xs"> 🔒 Active </Badge> </div> <p className="text-sm text-muted-foreground mb-4"> The page scroll is automatically locked while this modal is open. Try scrolling the background. </p> <Button onClick={onClose} className="w-full h-8 text-xs" > Close Modal </Button> </div> </div> )}export default UseScrollLockDemo
"use client"import { useRef, useState, useCallback } from "react"import { useIsomorphicLayoutEffect } from "@/components/ui/kibo-ui/use-isomorphic-layout-effect"type UseScrollLockOptions = { autoLock?: boolean lockTarget?: HTMLElement | string widthReflow?: boolean}type UseScrollLockReturn = { isLocked: boolean lock: () => void unlock: () => void}type OriginalStyle = { overflow: CSSStyleDeclaration["overflow"] paddingRight: CSSStyleDeclaration["paddingRight"]}const IS_SERVER = typeof window === "undefined"export function useScrollLock( options: UseScrollLockOptions = {},): UseScrollLockReturn { const { autoLock = true, lockTarget, widthReflow = true } = options const [isLocked, setIsLocked] = useState(false) const target = useRef<HTMLElement | null>(null) const originalStyle = useRef<OriginalStyle | null>(null) const lock = useCallback(() => { if (target.current) { const { overflow, paddingRight } = target.current.style // Save the original styles originalStyle.current = { overflow, paddingRight } // Prevent width reflow if (widthReflow) { // Use window inner width if body is the target as global scrollbar isn't part of the document const offsetWidth = target.current === document.body ? window.innerWidth : target.current.offsetWidth // Get current computed padding right in pixels const currentPaddingRight = parseInt(window.getComputedStyle(target.current).paddingRight, 10) || 0 const scrollbarWidth = offsetWidth - target.current.scrollWidth target.current.style.paddingRight = `${scrollbarWidth + currentPaddingRight}px` } // Lock the scroll target.current.style.overflow = "hidden" setIsLocked(true) } }, [widthReflow]) const unlock = useCallback(() => { if (target.current && originalStyle.current) { target.current.style.overflow = originalStyle.current.overflow // Only reset padding right if we changed it if (widthReflow) { target.current.style.paddingRight = originalStyle.current.paddingRight } } setIsLocked(false) }, [widthReflow]) useIsomorphicLayoutEffect(() => { if (IS_SERVER) return // Re-find the target element each time if (lockTarget) { target.current = typeof lockTarget === "string" ? document.querySelector(lockTarget) : lockTarget } if (!target.current) { target.current = document.body } if (autoLock) { lock() } return () => { unlock() } }, [autoLock, lockTarget, widthReflow, lock, unlock]) return { isLocked, lock, unlock }}export type { UseScrollLockOptions, UseScrollLockReturn }
Prevent scrolling with proper cleanup and reflow handling. Essential for modals, overlays, and mobile navigation menus.
React Browser API Hooks - Responsive Design, Dark Mode & Clipboard
Modern React hooks for browser APIs and responsive JavaScript development. Implement media queries, window size tracking, dark mode detection, clipboard operations, and viewport management for Next.js applications.
useMediaQuery
"use client"import { useMediaQuery } from "@/components/ui/shadcn-io/use-media-query"export function UseMediaQueryDemo() { const isLarge = useMediaQuery("(min-width: 1024px)") const isMedium = useMediaQuery("(min-width: 768px)") const isSmall = useMediaQuery("(max-width: 640px)") const isDarkMode = useMediaQuery("(prefers-color-scheme: dark)") const prefersReducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)") return ( <div className="space-y-4 rounded-lg border p-6"> <div> <h3 className="font-medium mb-2">Screen Size Detection</h3> <div className="space-y-2 text-sm"> <div className="flex items-center gap-2"> <div className={`w-3 h-3 rounded-full ${isLarge ? "bg-green-500" : "bg-gray-300"}`} /> <span>Large screen (≥1024px): {isLarge ? "Yes" : "No"}</span> </div> <div className="flex items-center gap-2"> <div className={`w-3 h-3 rounded-full ${isMedium ? "bg-green-500" : "bg-gray-300"}`} /> <span>Medium screen (≥768px): {isMedium ? "Yes" : "No"}</span> </div> <div className="flex items-center gap-2"> <div className={`w-3 h-3 rounded-full ${isSmall ? "bg-green-500" : "bg-gray-300"}`} /> <span>Small screen (≤640px): {isSmall ? "Yes" : "No"}</span> </div> </div> </div> <div> <h3 className="font-medium mb-2">User Preferences</h3> <div className="space-y-2 text-sm"> <div className="flex items-center gap-2"> <div className={`w-3 h-3 rounded-full ${isDarkMode ? "bg-green-500" : "bg-gray-300"}`} /> <span>Dark mode preference: {isDarkMode ? "Yes" : "No"}</span> </div> <div className="flex items-center gap-2"> <div className={`w-3 h-3 rounded-full ${prefersReducedMotion ? "bg-green-500" : "bg-gray-300"}`} /> <span>Reduced motion: {prefersReducedMotion ? "Yes" : "No"}</span> </div> </div> </div> <div className="text-xs text-muted-foreground"> <p>Try resizing your browser window or changing your system preferences to see the values update!</p> </div> </div> )}export default UseMediaQueryDemo
"use client"import { useState } from "react"import { useIsomorphicLayoutEffect } from "@/components/ui/kibo-ui/use-isomorphic-layout-effect"type UseMediaQueryOptions = { defaultValue?: boolean initializeWithValue?: boolean}const IS_SERVER = typeof window === "undefined"export function useMediaQuery( query: string, { defaultValue = false, initializeWithValue = true, }: UseMediaQueryOptions = {},): boolean { const getMatches = (query: string): boolean => { if (IS_SERVER) { return defaultValue } return window.matchMedia(query).matches } const [matches, setMatches] = useState<boolean>(() => { if (initializeWithValue) { return getMatches(query) } return defaultValue }) // Handles the change event of the media query. function handleChange() { setMatches(getMatches(query)) } useIsomorphicLayoutEffect(() => { const matchMedia = window.matchMedia(query) // Triggered at the first client-side load and if query changes handleChange() // Use deprecated `addListener` and `removeListener` to support Safari < 14 (#135) if (matchMedia.addListener) { matchMedia.addListener(handleChange) } else { matchMedia.addEventListener("change", handleChange) } return () => { if (matchMedia.removeListener) { matchMedia.removeListener(handleChange) } else { matchMedia.removeEventListener("change", handleChange) } } }, [query]) return matches}export type { UseMediaQueryOptions }
Responsive design with CSS media query matching. Perfect for adaptive layouts, responsive components, and device-specific behavior.
useWindowSize
"use client";import * as React from "react";import { useWindowSize } from "@/components/ui/shadcn-io/use-window-size";export default function UseWindowSizeDemo() { const { width = 0, height = 0 } = useWindowSize(); return ( <div className="flex flex-col items-center gap-3 py-4"> <div className="text-center"> <div className="text-2xl font-mono font-bold mb-2"> {width} × {height} </div> <div className="bg-muted rounded px-3 py-2 text-sm font-mono"> {JSON.stringify({ width, height })} </div> </div> <p className="text-center text-muted-foreground text-sm max-w-xs"> Resize your window to see live updates </p> </div> );}
Track window dimensions with debouncing and resize optimization. Essential for responsive calculations and dynamic layouts.
useScreen
"use client";import { useScreen } from '@/components/ui/shadcn-io/use-screen';export default function UseScreenDemo() { const screen = useScreen(); if (!screen) { return ( <div className="p-4 max-w-sm mx-auto"> <div className="text-center text-muted-foreground"> Screen information not available </div> </div> ); } const screenData = [ { label: 'Width', value: `${screen.width}px`, color: 'text-blue-600' }, { label: 'Height', value: `${screen.height}px`, color: 'text-green-600' }, { label: 'Available Width', value: `${screen.availWidth}px`, color: 'text-purple-600' }, { label: 'Available Height', value: `${screen.availHeight}px`, color: 'text-orange-600' }, { label: 'Color Depth', value: `${screen.colorDepth} bits`, color: 'text-red-600' }, { label: 'Pixel Depth', value: `${screen.pixelDepth} bits`, color: 'text-indigo-600' }, ]; return ( <div className="space-y-3 p-4 max-w-sm mx-auto"> <div className="space-y-1"> <h3 className="font-semibold">Screen Information</h3> <p className="text-xs text-muted-foreground"> Your display properties and dimensions </p> </div> <div className="space-y-1"> {screenData.slice(0, 4).map(({ label, value, color }) => ( <div key={label} className="flex justify-between items-center p-1.5 bg-muted rounded text-sm"> <span className="font-medium">{label}</span> <span className={`font-mono font-bold ${color}`}> {value} </span> </div> ))} </div> <div className="grid grid-cols-2 gap-1 text-xs"> <div className="p-1.5 bg-muted rounded"> <div className="font-medium">Color Depth</div> <div className="text-red-600 font-mono">{screen.colorDepth} bits</div> </div> <div className="p-1.5 bg-muted rounded"> <div className="font-medium">Pixel Depth</div> <div className="text-indigo-600 font-mono">{screen.pixelDepth} bits</div> </div> </div> <div className="text-xs text-muted-foreground text-center"> Values update automatically on screen changes </div> </div> );}
Access screen properties including resolution, color depth, and orientation. Perfect for device detection and display optimization.
useResizeObserver
"use client";import React, { useRef } from 'react';import { useResizeObserver } from '@/components/ui/shadcn-io/use-resize-observer';export default function UseResizeObserverDemo() { const ref = useRef<HTMLDivElement>(null); const { width = 0, height = 0 } = useResizeObserver({ ref: ref as React.RefObject<HTMLElement>, box: 'border-box', }); return ( <div className="space-y-4 p-4 max-w-sm mx-auto"> <div className="space-y-2"> <h3 className="text-lg font-semibold">Resize Observer</h3> <p className="text-sm text-muted-foreground"> Drag the resize handle to see dimensions update </p> </div> <div ref={ref} className="min-h-[120px] border-2 border-dashed border-blue-300 bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4 resize overflow-auto flex items-center justify-center" style={{ width: '100%' }} > <div className="text-center"> <div className="text-2xl font-bold text-blue-600 dark:text-blue-400"> {Math.round(width)} × {Math.round(height)} </div> <div className="text-xs text-muted-foreground mt-1"> Width × Height (px) </div> </div> </div> <div className="grid grid-cols-2 gap-2 text-xs"> <div className="p-2 bg-muted rounded"> <div className="font-medium">Width</div> <div className="text-green-600 font-mono">{Math.round(width)}px</div> </div> <div className="p-2 bg-muted rounded"> <div className="font-medium">Height</div> <div className="text-blue-600 font-mono">{Math.round(height)}px</div> </div> </div> <div className="text-xs text-muted-foreground text-center"> Drag the corner handle to resize the element </div> </div> );}
Observe element size changes with ResizeObserver API. Ideal for responsive components, charts, and dynamic content sizing.
useIntersectionObserver
"use client";import { useIntersectionObserver } from '@/components/ui/shadcn-io/use-intersection-observer';export default function UseIntersectionObserverDemo() { const { isIntersecting, ref } = useIntersectionObserver({ threshold: 0.3, }); return ( <div className="space-y-6 p-6"> {/* Status Display */} <div className="text-center space-y-2"> <h3 className="text-lg font-semibold">Intersection Observer</h3> <div className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${ isIntersecting ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300" : "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300" }`}> {isIntersecting ? '👁️ Visible (30%+ threshold)' : '🙈 Not visible'} </div> </div> {/* Scroll Area */} <div className="h-32 overflow-y-auto border rounded-lg bg-muted"> <div className="h-24 flex items-center justify-center text-sm text-muted-foreground"> Scroll down to see the target element </div> {/* Target Element */} <div ref={ref} className={` mx-4 my-8 p-6 border-2 border-dashed rounded-lg text-center transition-all duration-300 ${isIntersecting ? 'border-green-400 bg-green-50 dark:bg-green-900/20 scale-105' : 'border-gray-300 bg-white dark:bg-gray-700' } `} > <div className="text-2xl mb-2">{isIntersecting ? '🎯' : '📍'}</div> <div className="font-medium"> {isIntersecting ? 'I\'m visible!' : 'Target Element'} </div> <div className="text-sm text-muted-foreground mt-1"> 30% threshold required </div> </div> <div className="h-32 flex items-center justify-center text-sm text-muted-foreground"> Scroll up to hide the target element </div> </div> {/* Info */} <div className="text-center text-sm text-muted-foreground"> Scroll within the area above to trigger intersection detection </div> </div> );}
Efficient element visibility detection with threshold support. Perfect for lazy loading, infinite scroll, and viewport-based animations.
useDarkMode
"use client";import { useDarkMode } from '@/components/ui/shadcn-io/use-dark-mode';export default function UseDarkModeDemo() { const { isDarkMode, toggle, enable, disable } = useDarkMode({ localStorageKey: 'demo-basic-theme', applyDarkClass: false }); return ( <div className="flex flex-col items-center justify-center min-h-[300px] p-8 space-y-6"> {/* Status Display */} <div className="text-center space-y-2"> <h3 className="text-lg font-semibold">Dark Mode Control</h3> <div className={`px-6 py-3 rounded-lg text-lg font-medium ${ isDarkMode ? "bg-slate-800 text-slate-100 dark:bg-slate-700" : "bg-yellow-100 text-yellow-900 dark:bg-yellow-900/30" }`}> {isDarkMode ? '🌙 Dark Mode' : '☀️ Light Mode'} </div> </div> {/* Control Buttons */} <div className="flex flex-wrap gap-3 justify-center"> <button onClick={toggle} className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors" > Toggle Theme </button> <button onClick={enable} className="px-4 py-2 bg-slate-700 text-white rounded-md hover:bg-slate-800 transition-colors disabled:opacity-50" disabled={isDarkMode} > Enable Dark </button> <button onClick={disable} className="px-4 py-2 bg-yellow-600 text-white rounded-md hover:bg-yellow-700 transition-colors disabled:opacity-50" disabled={!isDarkMode} > Enable Light </button> </div> {/* Info */} <div className="text-center space-y-2"> <div className="text-sm text-muted-foreground"> <p>Theme preference is saved to localStorage</p> <p>Respects system preference on first load</p> </div> <code className="bg-muted px-2 py-1 rounded text-xs"> localStorage: {isDarkMode ? 'true' : 'false'} </code> </div> </div> );}
Complete dark mode implementation with system preference detection and localStorage persistence. Essential for modern applications.
useCopyToClipboard
"use client";import { ClipboardCheckIcon, ClipboardIcon } from "lucide-react";import { toast } from "sonner";import { Button } from "@/components/ui/button";import { useCopyToClipboard } from "@/components/ui/shadcn-io/use-copy-to-clipboard";export default function UseCopyToClipboardDemo() { const [copy, isCopied] = useCopyToClipboard(); return ( <Button variant="outline" className="gap-2 text-sm" onClick={() => copy("Hello world").then(() => toast("Text Copied to your clipboard 🎉.") ) } > Click me to copy {isCopied ? ( <ClipboardCheckIcon size={16} /> ) : ( <ClipboardIcon size={16} /> )} </Button> );}
import * as React from "react"import { Slot } from "@radix-ui/react-slot"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "@/lib/utils"const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, })function Button({ className, variant, size, asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> )}export { Button, buttonVariants }
Reliable clipboard operations with fallback support and success tracking. Perfect for code snippets, sharing features, and user convenience.
React Performance Hooks - Debouncing, API Optimization & Memory Management
High-performance React hooks for JavaScript optimization and utility functions. Implement debounced API calls, timeout management, memory leak prevention, and performance optimizations for TypeScript applications.
useDebounceValue
"use client";import * as React from "react";import { Input } from "@/components/ui/input";import { Label } from "@/components/ui/label";import { useDebounceValue } from "@/components/ui/shadcn-io/use-debounce-value";export default function UseDebounceValueDemo() { const [debouncedValue, setValue] = useDebounceValue("John", 500); return ( <div className="flex flex-col items-center gap-4 p-4"> <div className="w-full max-w-sm space-y-4"> <div className="space-y-2"> <Label htmlFor="name">Type to see debounced value</Label> <Input id="name" type="text" defaultValue="John" onChange={(event) => setValue(event.target.value)} placeholder="Enter your name..." /> </div> <div className="text-center space-y-2"> <div className="text-lg font-semibold"> Debounced value: <span className="text-primary">{debouncedValue}</span> </div> <p className="text-sm text-muted-foreground"> Updates 500ms after you stop typing </p> </div> </div> </div> );}
import * as React from "react"import { cn } from "@/lib/utils"function Input({ className, type, ...props }: React.ComponentProps<"input">) { return ( <input type={type} data-slot="input" className={cn( "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className )} {...props} /> )}export { Input }
Debounce values to prevent excessive updates and API calls. Essential for search inputs, resize handlers, and expensive computations.
useDebounceCallback
"use client"import * as React from "react"import { Input } from "@/components/ui/input"import { useDebounceCallback } from "@/components/ui/shadcn-io/use-debounce-callback"export default function UseDebounceCallbackDemo() { const [value, setValue] = React.useState("") const debounced = useDebounceCallback(setValue, 500) return ( <div className="w-full max-w-sm space-y-4"> <p className="text-sm text-muted-foreground">Debounced value: {value}</p> <Input type="text" placeholder="Type something..." defaultValue={value} onChange={(event) => debounced(event.target.value)} /> </div> )}
import * as React from "react"import { cn } from "@/lib/utils"function Input({ className, type, ...props }: React.ComponentProps<"input">) { return ( <input type={type} data-slot="input" className={cn( "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className )} {...props} /> )}export { Input }
Debounce function calls with cancellation support. Perfect for API requests, form validation, and event handlers.
useTimeout
"use client";import * as React from "react";import { Button } from "@/components/ui/button";import { useTimeout } from "@/components/ui/shadcn-io/use-timeout";export default function UseTimeoutDemo() { const [message, setMessage] = React.useState("Click 'Start Timer' to begin"); const [isTimerActive, setIsTimerActive] = React.useState(false); const [showResult, setShowResult] = React.useState(false); const [progress, setProgress] = React.useState(0); // Single 5-second timeout that shows result useTimeout(() => { setMessage("5 seconds have passed! ⏰"); setIsTimerActive(false); setShowResult(true); setProgress(100); }, isTimerActive ? 5000 : null); // Progress bar animation using interval React.useEffect(() => { if (!isTimerActive) { setProgress(0); return; } const startTime = Date.now(); const duration = 5000; // 5 seconds const interval = setInterval(() => { const elapsed = Date.now() - startTime; const newProgress = Math.min((elapsed / duration) * 100, 100); setProgress(newProgress); if (newProgress >= 100) { clearInterval(interval); } }, 50); // Update every 50ms for smooth animation return () => clearInterval(interval); }, [isTimerActive]); const startTimer = () => { setMessage("Timer is running... (5 seconds)"); setIsTimerActive(true); setShowResult(false); setProgress(0); }; const resetTimer = () => { setMessage("Click 'Start Timer' to begin"); setIsTimerActive(false); setShowResult(false); setProgress(0); }; return ( <div className="flex flex-col items-center justify-center min-h-[300px] p-8 space-y-6"> <div className="text-center space-y-4"> <h3 className="text-xl font-semibold">5-Second Timer</h3> <div className="relative"> <div className="text-2xl font-medium text-center mb-4"> {showResult ? "🎉" : isTimerActive ? "⏳" : "⏱️"} </div> <p className="text-lg"> {message} </p> </div> {/* Progress indication */} {isTimerActive && ( <div className="w-full max-w-xs bg-muted rounded-full h-3"> <div className="h-3 bg-primary rounded-full transition-all duration-75 ease-out" style={{ width: `${progress}%` }} /> </div> )} <div className="text-sm text-muted-foreground"> Status: {showResult ? "Complete" : isTimerActive ? "Timer Running" : "Ready"} </div> </div> <div className="flex gap-3"> {!isTimerActive && !showResult && ( <Button onClick={startTimer}> Start 5s Timer </Button> )} {isTimerActive && ( <Button onClick={resetTimer} variant="outline"> Cancel Timer </Button> )} {showResult && ( <Button onClick={resetTimer}> Reset Timer </Button> )} </div> <div className="text-sm text-muted-foreground text-center max-w-sm"> This demonstrates a single 5-second timeout that executes once and can be cancelled by passing null to the delay. </div> </div> );}
import * as React from "react"import { Slot } from "@radix-ui/react-slot"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "@/lib/utils"const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, })function Button({ className, variant, size, asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> )}export { Button, buttonVariants }
Robust timeout management with cleanup and control methods. Ideal for notifications, delayed actions, and temporary states.
useInterval
"use client"import * as React from "react"import { useInterval } from "@/components/ui/shadcn-io/use-interval"export default function UseIntervalDemo() { const colors = React.useMemo( () => [ "221.2 83.2% 53.3%", "346.8 77.2% 49.8%", "24.6 95% 53.1%", "142.1 76.2% 36.3%", "47.9 95.8% 53.1%", "0 72.2% 50.6%", "262.1 83.3% 57.8%", ], [] ) const [index, setIndex] = React.useState(0) useInterval(() => setIndex((index) => index + 1), 1000) const color = colors[index % colors.length] return ( <div className="size-48 rounded-xl transition-all duration-300 ease-in" style={{ background: `hsl(${color})`, }} /> )}
Reliable interval execution with automatic cleanup. Perfect for polling, real-time updates, and periodic tasks.
useUnmount
"use client";import { useUnmount } from '@/components/ui/shadcn-io/use-unmount';import { useState, useEffect } from 'react';function ComponentWithCleanup() { const [message, setMessage] = useState("Component is mounted"); // This will run when the component unmounts useUnmount(() => { console.log("Cleanup: Component is unmounting"); // In a real scenario, you might: // - Cancel API requests // - Clear timeouts/intervals // - Unsubscribe from events // - Save data to localStorage }); // Simulate some work that needs cleanup useEffect(() => { const timer = setInterval(() => { setMessage(`Component mounted at ${new Date().toLocaleTimeString()}`); }, 1000); // This cleanup will also run on unmount return () => { console.log("Cleanup: Clearing interval"); clearInterval(timer); }; }, []); return ( <div className="p-6 border rounded-lg bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800"> <h4 className="text-lg font-semibold text-green-800 dark:text-green-200 mb-2"> Component with Cleanup </h4> <p className="text-green-700 dark:text-green-300 text-sm mb-3"> {message} </p> <p className="text-xs text-green-600 dark:text-green-400"> Check the console for cleanup messages when this component unmounts </p> </div> );}export default function UseUnmountDemo() { const [showComponent, setShowComponent] = useState(true); return ( <div className="flex flex-col items-center justify-center min-h-[400px] p-8 space-y-6"> {/* Control Button */} <div className="text-center space-y-4"> <h3 className="text-lg font-semibold">useUnmount Hook Demo</h3> <button onClick={() => setShowComponent(!showComponent)} className={`px-6 py-2 rounded-md font-medium transition-colors ${ showComponent ? "bg-red-600 hover:bg-red-700 text-white" : "bg-green-600 hover:bg-green-700 text-white" }`} > {showComponent ? 'Unmount Component' : 'Mount Component'} </button> </div> {/* Component that can be mounted/unmounted */} <div className="w-full max-w-md"> {showComponent ? ( <ComponentWithCleanup /> ) : ( <div className="p-6 border rounded-lg bg-gray-50 dark:bg-gray-900/50 border-gray-200 dark:border-gray-800"> <p className="text-gray-600 dark:text-gray-400 text-center"> Component is unmounted. Check console for cleanup messages. </p> </div> )} </div> {/* Usage Info */} <div className="text-center text-sm text-muted-foreground max-w-md"> <p> This demo shows how <code className="bg-muted px-1 py-0.5 rounded">useUnmount</code> {" "}runs cleanup logic when a component is removed from the DOM. </p> </div> </div> );}
Execute cleanup code when components unmount. Essential for subscriptions, event listeners, and resource disposal.
useIsMounted
"use client";import { useEffect, useState } from 'react';import { useIsMounted } from '@/components/ui/shadcn-io/use-is-mounted';const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));function AsyncChild() { const [status, setStatus] = useState('⏳ Loading...'); const isMounted = useIsMounted(); useEffect(() => { // Simulate async operation void delay(2000).then(() => { if (isMounted()) { setStatus('✅ Data loaded successfully'); } }); }, [isMounted]); return ( <div className="p-4 bg-muted rounded border-2 border-dashed"> <div className="text-sm font-medium mb-2">Async Component</div> <div className="text-sm">{status}</div> <div className="text-xs text-muted-foreground mt-2"> This component checks if it's still mounted before updating state </div> </div> );}export default function UseIsMountedDemo() { const [showChild, setShowChild] = useState(false); const toggleChild = () => { setShowChild(prev => !prev); }; return ( <div className="flex flex-col items-center justify-center min-h-[300px] p-8 space-y-6"> {/* Status Display */} <div className="text-center space-y-2"> <h3 className="text-lg font-semibold">Mount State Tracking</h3> <div className={`px-4 py-2 rounded-lg text-sm font-medium ${ showChild ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300" : "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300" }`}> Component: {showChild ? '🟢 Mounted' : '🔴 Unmounted'} </div> </div> {/* Toggle Button */} <button onClick={toggleChild} className="px-6 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors font-medium" > {showChild ? 'Unmount Component' : 'Mount Component'} </button> {/* Child Component */} <div className="w-full max-w-md"> {showChild && <AsyncChild />} </div> {/* Instructions */} <div className="text-center text-sm text-muted-foreground max-w-md"> <p className="mb-2"> Click "Mount Component" and quickly click "Unmount" before the 2-second delay finishes. </p> <p> The hook prevents state updates on unmounted components, avoiding memory leaks. </p> </div> </div> );}
Prevent memory leaks by checking mount status before state updates. Critical for async operations and cleanup.
useIsClient
"use client"import { useIsClient } from "@/components/ui/shadcn-io/use-is-client"export function UseIsClientDemo() { const isClient = useIsClient() return ( <div className="space-y-4 p-6"> <div className="text-center space-y-2"> <div className="text-2xl font-bold"> {isClient ? ( <span className="text-green-600">🌐 Client</span> ) : ( <span className="text-orange-600">🖥️ Server</span> )} </div> <p className="text-sm text-muted-foreground"> Code is running on: {isClient ? "Client side" : "Server side"} </p> </div> <div className="space-y-3 text-sm"> <div className="flex justify-between items-center p-2 bg-muted rounded"> <span>Browser APIs available:</span> <span className={`font-mono ${isClient ? "text-green-600" : "text-red-600"}`}> {isClient ? "Yes" : "No"} </span> </div> <div className="flex justify-between items-center p-2 bg-muted rounded"> <span>Window object:</span> <span className={`font-mono ${isClient ? "text-green-600" : "text-red-600"}`}> {isClient ? "Available" : "Undefined"} </span> </div> <div className="flex justify-between items-center p-2 bg-muted rounded"> <span>Document object:</span> <span className={`font-mono ${isClient ? "text-green-600" : "text-red-600"}`}> {isClient ? "Available" : "Undefined"} </span> </div> {isClient && ( <div className="flex justify-between items-center p-2 bg-green-50 border border-green-200 rounded"> <span>User Agent:</span> <span className="font-mono text-xs text-green-700 truncate ml-2"> {navigator.userAgent.split(' ')[0]}... </span> </div> )} </div> <div className="text-xs text-muted-foreground bg-muted p-3 rounded"> <p><strong>Note:</strong> During SSR, this will initially show "Server" then switch to "Client" after hydration.</p> </div> </div> )}export default UseIsClientDemo
"use client"import { useEffect, useState } from "react"export function useIsClient() { const [isClient, setClient] = useState(false) useEffect(() => { setClient(true) }, []) return isClient}
Distinguish between server and client rendering. Essential for SSR applications and browser-specific features.
useEventCallback
"use client";import * as React from "react";import { Button } from "@/components/ui/button";import { useEventCallback } from "@/components/ui/shadcn-io/use-event-callback";export default function UseEventCallbackDemo() { const [count, setCount] = React.useState(0); const [message, setMessage] = React.useState("Click a button below"); // Memoized event callback that always has access to the latest count const handleClick = useEventCallback((event: React.MouseEvent<HTMLButtonElement>) => { const action = event.currentTarget.dataset.action; setMessage(`Button clicked! Current count was: ${count}`); if (action === "increment") { setCount(count + 1); } else if (action === "decrement") { setCount(count - 1); } else if (action === "reset") { setCount(0); } }); return ( <div className="flex flex-col items-center gap-6"> <div className="text-center space-y-2"> <div className="text-4xl font-bold">{count}</div> <p className="text-muted-foreground">{message}</p> </div> <div className="flex gap-3"> <Button onClick={handleClick} data-action="increment" > Increment </Button> <Button onClick={handleClick} data-action="decrement" variant="outline" > Decrement </Button> <Button onClick={handleClick} data-action="reset" variant="secondary" > Reset </Button> </div> <div className="text-sm text-muted-foreground text-center max-w-md"> <p> The event callback is memoized and always has access to the latest count value, preventing unnecessary re-renders while maintaining fresh closures. </p> </div> </div> );}
import * as React from "react"import { Slot } from "@radix-ui/react-slot"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "@/lib/utils"const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, })function Button({ className, variant, size, asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> )}export { Button, buttonVariants }
Create stable callback references that don't break memoization. Perfect for performance optimization and dependency arrays.
useEventListener
"use client"import React, { useRef, useState } from "react"import { useEventListener } from "@/components/ui/shadcn-io/use-event-listener"export function UseEventListenerDemo() { const [scrollY, setScrollY] = useState(0) const [clicks, setClicks] = useState(0) const [visibility, setVisibility] = useState(!document.hidden) const [mousePos, setMousePos] = useState({ x: 0, y: 0 }) const buttonRef = useRef<HTMLButtonElement>(null) const documentRef = useRef<Document>(document) const divRef = useRef<HTMLDivElement>(null) // Window scroll event const onScroll = () => { setScrollY(window.scrollY) } // Button click event const onClick = () => { setClicks((prev) => prev + 1) } // Document visibility change event const onVisibilityChange = () => { setVisibility(!document.hidden) } // Mouse move event on div const onMouseMove = (event: MouseEvent) => { if (divRef.current) { const rect = divRef.current.getBoundingClientRect() setMousePos({ x: event.clientX - rect.left, y: event.clientY - rect.top, }) } } // Attach event listeners useEventListener("scroll", onScroll) useEventListener("click", onClick, buttonRef as React.RefObject<HTMLElement>) useEventListener("visibilitychange", onVisibilityChange, documentRef) useEventListener("mousemove", onMouseMove, divRef as React.RefObject<HTMLElement>) return ( <div className="space-y-4"> <div className="space-y-3 p-4"> <h3 className="text-lg font-semibold text-center">Event Listener Demo</h3> <div className="grid grid-cols-2 gap-3 text-sm"> <div className="space-y-1"> <div className="flex justify-between"> <span className="text-xs">Scroll Y:</span> <span className="font-mono text-xs">{scrollY}px</span> </div> <div className="flex justify-between"> <span className="text-xs">Clicks:</span> <span className="font-mono text-xs">{clicks}</span> </div> <div className="flex justify-between"> <span className="text-xs">Tab visible:</span> <span className={`font-mono text-xs ${visibility ? "text-green-600" : "text-red-600"}`}> {visibility ? "Yes" : "No"} </span> </div> </div> <div className="space-y-1"> <span className="text-xs">Mouse position:</span> <div className="font-mono text-xs"> X: {mousePos.x.toFixed(0)}, Y: {mousePos.y.toFixed(0)} </div> </div> </div> <button ref={buttonRef} className="rounded bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90" > Click me ({clicks}) </button> <div ref={divRef} className="h-20 rounded-lg bg-muted p-3 relative" > <p className="text-xs text-muted-foreground"> Move mouse here </p> <div className="absolute bottom-1 right-2 text-xs font-mono"> {mousePos.x.toFixed(0)}, {mousePos.y.toFixed(0)} </div> </div> <div className="text-xs text-muted-foreground"> Scroll • Switch tabs • Click button • Move mouse in area </div> </div> {/* Add some scroll content */} <div className="h-64 rounded-lg bg-muted p-4 flex items-center justify-center"> <p className="text-sm text-muted-foreground">Scroll to see Y value change</p> </div> </div> )}export default UseEventListenerDemo
"use client"import { useEffect, useRef } from "react"import type { RefObject } from "react"import { useIsomorphicLayoutEffect } from "@/components/ui/kibo-ui/use-isomorphic-layout-effect"// MediaQueryList Event based useEventListener interfacefunction useEventListener<K extends keyof MediaQueryListEventMap>( eventName: K, handler: (event: MediaQueryListEventMap[K]) => void, element: RefObject<MediaQueryList>, options?: boolean | AddEventListenerOptions,): void// Window Event based useEventListener interfacefunction useEventListener<K extends keyof WindowEventMap>( eventName: K, handler: (event: WindowEventMap[K]) => void, element?: undefined, options?: boolean | AddEventListenerOptions,): void// Element Event based useEventListener interfacefunction useEventListener< K extends keyof HTMLElementEventMap & keyof SVGElementEventMap, T extends Element = K extends keyof HTMLElementEventMap ? HTMLDivElement : SVGElement,>( eventName: K, handler: | ((event: HTMLElementEventMap[K]) => void) | ((event: SVGElementEventMap[K]) => void), element: RefObject<T>, options?: boolean | AddEventListenerOptions,): void// Document Event based useEventListener interfacefunction useEventListener<K extends keyof DocumentEventMap>( eventName: K, handler: (event: DocumentEventMap[K]) => void, element: RefObject<Document>, options?: boolean | AddEventListenerOptions,): voidfunction useEventListener< KW extends keyof WindowEventMap, KH extends keyof HTMLElementEventMap & keyof SVGElementEventMap, KM extends keyof MediaQueryListEventMap, T extends HTMLElement | SVGAElement | MediaQueryList = HTMLElement,>( eventName: KW | KH | KM, handler: ( event: | WindowEventMap[KW] | HTMLElementEventMap[KH] | SVGElementEventMap[KH] | MediaQueryListEventMap[KM] | Event, ) => void, element?: RefObject<T>, options?: boolean | AddEventListenerOptions,) { // Create a ref that stores handler const savedHandler = useRef(handler) useIsomorphicLayoutEffect(() => { savedHandler.current = handler }, [handler]) useEffect(() => { // Define the listening target const targetElement: T | Window = element?.current ?? window if (!(targetElement && targetElement.addEventListener)) return // Create event listener that calls handler function stored in ref const listener: typeof handler = (event) => { savedHandler.current(event) } targetElement.addEventListener(eventName, listener, options) // Remove event listener on cleanup return () => { targetElement.removeEventListener(eventName, listener, options) } }, [eventName, element, options])}export { useEventListener }
Declarative event listener management with automatic cleanup. Ideal for keyboard shortcuts, scroll handlers, and window events.
useIsomorphicLayoutEffect
"use client";import { useIsomorphicLayoutEffect } from '@/components/ui/shadcn-io/use-isomorphic-layout-effect';import { useState, useRef } from 'react';export default function UseIsomorphicLayoutEffectDemo() { const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); const [isClient, setIsClient] = useState(false); const [effectType, setEffectType] = useState(''); const elementRef = useRef<HTMLDivElement>(null); useIsomorphicLayoutEffect(() => { // This runs as useLayoutEffect in browser, useEffect during SSR setIsClient(true); setEffectType(typeof window !== 'undefined' ? 'useLayoutEffect' : 'useEffect'); console.log( "In the browser, I'm an `useLayoutEffect`, but in SSR, I'm an `useEffect`." ); // Measure element dimensions (browser only) if (elementRef.current) { const rect = elementRef.current.getBoundingClientRect(); setDimensions({ width: Math.round(rect.width), height: Math.round(rect.height) }); } }, []); return ( <div className="flex flex-col items-center justify-center p-6 space-y-4"> {/* Status Display */} <div className="text-center space-y-3"> <h3 className="text-lg font-semibold">Isomorphic Layout Effect</h3> <div className="flex gap-3 justify-center"> <div className="px-3 py-2 bg-muted rounded text-xs"> <span className="text-muted-foreground">Env:</span>{' '} <code className="font-mono"> {isClient ? 'Client' : 'SSR'} </code> </div> <div className="px-3 py-2 bg-muted rounded text-xs"> <span className="text-muted-foreground">Type:</span>{' '} <code className="font-mono"> {effectType || 'Loading'} </code> </div> </div> </div> {/* Measured Element */} <div ref={elementRef} className="bg-primary text-primary-foreground p-4 rounded-lg min-w-[180px] text-center" > <h4 className="font-semibold text-sm mb-1">Measured Element</h4> <p className="text-xs opacity-90"> Dimensions measured on mount </p> </div> {/* Dimensions Display */} {isClient && dimensions.width > 0 && ( <div className="flex gap-3 text-sm"> <div className="px-3 py-1.5 bg-muted rounded"> <span className="text-muted-foreground text-xs">W:</span> <code className="ml-1 font-mono">{dimensions.width}px</code> </div> <div className="px-3 py-1.5 bg-muted rounded"> <span className="text-muted-foreground text-xs">H:</span> <code className="ml-1 font-mono">{dimensions.height}px</code> </div> </div> )} {/* Explanation */} <div className="max-w-sm text-center text-xs text-muted-foreground"> Uses <code className="bg-muted px-1 rounded">useLayoutEffect</code> in browser, <code className="bg-muted px-1 rounded mx-1">useEffect</code> during SSR </div> </div> );}
SSR-safe useLayoutEffect replacement. Essential for DOM measurements and synchronous updates.
React Script Loading & Dynamic Content Hooks
Advanced React hooks for dynamic content and external script management in JavaScript applications. Load third-party scripts, manage document titles, and handle dynamic imports with proper TypeScript support.
useScript
"use client";import { useScript } from '@/components/ui/shadcn-io/use-script';export default function UseScriptDemo() { const status = useScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js', { removeOnUnmount: false, id: 'confetti-script', }); const triggerConfetti = () => { if (status === 'ready' && typeof window !== 'undefined' && 'confetti' in window) { // @ts-ignore - confetti is loaded dynamically window.confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); } }; const getStatusColor = (status: string) => { switch (status) { case 'loading': return 'text-blue-600'; case 'ready': return 'text-green-600'; case 'error': return 'text-red-600'; default: return 'text-gray-600'; } }; const getStatusIcon = (status: string) => { switch (status) { case 'loading': return '⏳'; case 'ready': return '✅'; case 'error': return '❌'; default: return '⚪'; } }; return ( <div className="space-y-3 p-4 max-w-sm mx-auto"> <div className="space-y-1"> <h3 className="font-semibold">Script Loader</h3> <p className="text-xs text-muted-foreground"> Dynamically loads canvas-confetti library </p> </div> <div className="p-2 bg-muted rounded"> <div className="flex items-center justify-between"> <span className="text-sm font-medium">Status:</span> <div className="flex items-center gap-1"> <span>{getStatusIcon(status)}</span> <span className={`text-sm font-mono font-bold ${getStatusColor(status)}`}> {status} </span> </div> </div> </div> <div className="space-y-2"> <button onClick={triggerConfetti} disabled={status !== 'ready'} className="w-full px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" > {status === 'ready' ? '🎉 Trigger Confetti!' : 'Loading script...'} </button> {status === 'ready' && ( <p className="text-xs text-green-600 text-center"> Script loaded! Click the button to test. </p> )} {status === 'error' && ( <p className="text-xs text-red-600 text-center"> Failed to load script. Check your connection. </p> )} </div> <div className="text-xs text-muted-foreground text-center"> Script status updates automatically </div> </div> );}
Dynamically load external scripts with loading state tracking. Perfect for third-party integrations, CDN resources, and conditional loading.
useDocumentTitle
"use client";import * as React from "react";import { Button } from "@/components/ui/button";import { useDocumentTitle } from "@/components/ui/shadcn-io/use-document-title";export default function UseDocumentTitleDemo() { const [counter, setCounter] = React.useState(0); useDocumentTitle(`Clicked ${counter} times`); return ( <div className="flex flex-col items-center gap-4"> <Button className="w-fit" onClick={() => setCounter(counter + 1)}> Increment Counter: {counter} </Button> <p className="w-3/4 text-center text-muted-foreground"> Click to increment the counter and watch the document title update in real-time! </p> </div> );}
import * as React from "react"import { Slot } from "@radix-ui/react-slot"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "@/lib/utils"const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, })function Button({ className, variant, size, asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> )}export { Button, buttonVariants }
Dynamically update document title with automatic cleanup. Essential for single-page applications and dynamic routing.
Advanced React Utility Hooks - Countdown Timers & Theme Management
Specialized React hooks for advanced use cases in JavaScript and TypeScript projects. Implement countdown timers, advanced dark mode switching, and read-only storage access for complex React applications.
useCountdown
"use client"import { useState } from "react"import type { ChangeEvent } from "react"import { useCountdown } from "@/components/ui/shadcn-io/use-countdown"export function UseCountdownDemo() { const [intervalValue, setIntervalValue] = useState<number>(1000) const [count, { startCountdown, stopCountdown, resetCountdown }] = useCountdown({ countStart: 30, intervalMs: intervalValue, }) const handleChangeIntervalValue = (event: ChangeEvent<HTMLInputElement>) => { setIntervalValue(Number(event.target.value)) } return ( <div className="space-y-4 p-6"> <div className="text-center"> <div className="text-3xl font-mono font-bold text-primary mb-2"> {count} </div> <p className="text-sm text-muted-foreground"> Countdown Timer </p> </div> <div className="flex gap-2 justify-center"> <button onClick={startCountdown} disabled={count === 0} className="rounded bg-primary px-3 py-2 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50" > Start </button> <button onClick={stopCountdown} className="rounded border border-input bg-background px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground" > Stop </button> <button onClick={resetCountdown} className="rounded border border-input bg-background px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground" > Reset </button> </div> <div className="space-y-2"> <label className="text-sm font-medium"> Speed: {intervalValue}ms </label> <input type="range" min="200" max="2000" step="200" value={intervalValue} onChange={handleChangeIntervalValue} className="w-full" /> </div> </div> )}export default UseCountdownDemo
"use client"import { useCallback } from "react"import { useBoolean } from "@/components/ui/kibo-ui/use-boolean"import { useCounter } from "@/components/ui/kibo-ui/use-counter"import { useInterval } from "@/components/ui/kibo-ui/use-interval"type CountdownOptions = { countStart: number intervalMs?: number isIncrement?: boolean countStop?: number}type CountdownControllers = { startCountdown: () => void stopCountdown: () => void resetCountdown: () => void}export function useCountdown({ countStart, countStop = 0, intervalMs = 1000, isIncrement = false,}: CountdownOptions): [number, CountdownControllers] { const { count, increment, decrement, reset: resetCounter, } = useCounter(countStart) /* * Note: used to control the useInterval * running: If true, the interval is running * start: Should set running true to trigger interval * stop: Should set running false to remove interval. */ const { value: isCountdownRunning, setTrue: startCountdown, setFalse: stopCountdown, } = useBoolean(false) // Will set running false and reset the seconds to initial value. const resetCountdown = useCallback(() => { stopCountdown() resetCounter() }, [stopCountdown, resetCounter]) const countdownCallback = useCallback(() => { if (count === countStop) { stopCountdown() return } if (isIncrement) { increment() } else { decrement() } }, [count, countStop, decrement, increment, isIncrement, stopCountdown]) useInterval(countdownCallback, isCountdownRunning ? intervalMs : null) return [count, { startCountdown, stopCountdown, resetCountdown }]}export type { CountdownOptions, CountdownControllers }
Feature-rich countdown timer with play/pause controls. Perfect for timers, limited-time offers, and time-sensitive features.
useTernaryDarkMode
"use client"import { Button } from "@/components/ui/button"import { Badge } from "@/components/ui/badge"import { Label } from "@/components/ui/label"import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/ui/select"import { useTernaryDarkMode, type TernaryDarkMode,} from "@/components/ui/shadcn-io/use-ternary-dark-mode"export function UseTernaryDarkModeDemo() { const { isDarkMode, ternaryDarkMode, setTernaryDarkMode, toggleTernaryDarkMode, } = useTernaryDarkMode() const getModeIcon = (mode: string) => { switch (mode) { case 'light': return '☀️' case 'dark': return '🌙' case 'system': return '💻' default: return '🎨' } } const getModeColor = (mode: string) => { switch (mode) { case 'light': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300' case 'dark': return 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300' case 'system': return 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300' default: return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300' } } return ( <div className="space-y-3 p-4 max-w-sm mx-auto"> <div className="space-y-2"> <div className="flex items-center justify-between"> <Label className="text-sm font-medium">Ternary Dark Mode</Label> <Badge className={`text-xs ${getModeColor(ternaryDarkMode)}`}> {getModeIcon(ternaryDarkMode)} {ternaryDarkMode} </Badge> </div> </div> <div className="p-3 bg-muted rounded-lg text-center"> <div className="text-2xl mb-2"> {isDarkMode ? '🌙' : '☀️'} </div> <div className="text-sm font-medium"> Current Theme: {isDarkMode ? 'Dark' : 'Light'} </div> <div className="text-xs text-muted-foreground mt-1"> Resolved from {ternaryDarkMode} preference </div> </div> <div className="space-y-2"> <Label className="text-sm font-medium">Theme Controls</Label> <div className="grid grid-cols-3 gap-1"> <Button onClick={() => setTernaryDarkMode('light')} variant={ternaryDarkMode === 'light' ? 'default' : 'outline'} size="sm" className="h-8 text-xs" > ☀️ Light </Button> <Button onClick={() => setTernaryDarkMode('system')} variant={ternaryDarkMode === 'system' ? 'default' : 'outline'} size="sm" className="h-8 text-xs" > 💻 System </Button> <Button onClick={() => setTernaryDarkMode('dark')} variant={ternaryDarkMode === 'dark' ? 'default' : 'outline'} size="sm" className="h-8 text-xs" > 🌙 Dark </Button> </div> </div> <div className="space-y-2"> <Label className="text-sm font-medium">Quick Toggle</Label> <Button onClick={toggleTernaryDarkMode} variant="outline" size="sm" className="w-full h-8 text-xs" > 🔄 Toggle from {ternaryDarkMode} </Button> </div> <div className="space-y-2"> <Label className="text-sm font-medium">Dropdown Selector</Label> <Select value={ternaryDarkMode} onValueChange={(value) => setTernaryDarkMode(value as TernaryDarkMode)} > <SelectTrigger className="h-8 text-xs"> <SelectValue /> </SelectTrigger> <SelectContent> <SelectItem value="light">☀️ Light Mode</SelectItem> <SelectItem value="system">💻 System Preference</SelectItem> <SelectItem value="dark">🌙 Dark Mode</SelectItem> </SelectContent> </Select> </div> <div className="text-xs text-muted-foreground text-center"> System mode follows your OS preference automatically </div> </div> )}export default UseTernaryDarkModeDemo
"use client"import type { Dispatch, SetStateAction } from "react"import { useLocalStorage } from "@/components/ui/kibo-ui/use-local-storage"import { useMediaQuery } from "@/components/ui/kibo-ui/use-media-query"const COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)"const LOCAL_STORAGE_KEY = "use-ternary-dark-mode"export type TernaryDarkMode = "system" | "dark" | "light"export type TernaryDarkModeOptions = { defaultValue?: TernaryDarkMode localStorageKey?: string initializeWithValue?: boolean}export type TernaryDarkModeReturn = { isDarkMode: boolean ternaryDarkMode: TernaryDarkMode setTernaryDarkMode: Dispatch<SetStateAction<TernaryDarkMode>> toggleTernaryDarkMode: () => void}export function useTernaryDarkMode({ defaultValue = "system", localStorageKey = LOCAL_STORAGE_KEY, initializeWithValue = true,}: TernaryDarkModeOptions = {}): TernaryDarkModeReturn { const isDarkOS = useMediaQuery(COLOR_SCHEME_QUERY, { initializeWithValue }) const [mode, setMode] = useLocalStorage(localStorageKey, defaultValue, { initializeWithValue, }) const isDarkMode = mode === "dark" || (mode === "system" && isDarkOS) const toggleTernaryDarkMode = () => { const modes: TernaryDarkMode[] = ["light", "system", "dark"] setMode((prevMode): TernaryDarkMode => { const nextIndex = (modes.indexOf(prevMode) + 1) % modes.length return modes[nextIndex] }) } return { isDarkMode, ternaryDarkMode: mode, setTernaryDarkMode: setMode, toggleTernaryDarkMode, }}
Advanced dark mode with system/light/dark states. Ideal for applications offering system preference following.
useReadLocalStorage
"use client"import { useState } from "react"import { useReadLocalStorage } from "@/components/ui/shadcn-io/use-read-local-storage"export function UseReadLocalStorageDemo() { const [inputKey, setInputKey] = useState("demo-key") // Read values from localStorage for different keys const demoValue = useReadLocalStorage<string>("demo-key") const userPrefs = useReadLocalStorage<{ theme: string; lang: string }>("user-prefs") const count = useReadLocalStorage<number>("counter") const customValue = useReadLocalStorage<string>(inputKey) // Function to set some example values for demonstration const setExampleValues = () => { localStorage.setItem("demo-key", JSON.stringify("Hello World")) localStorage.setItem("user-prefs", JSON.stringify({ theme: "dark", lang: "en" })) localStorage.setItem("counter", JSON.stringify(42)) localStorage.setItem("custom-key", JSON.stringify("Custom Value")) // Trigger storage events to update hooks window.dispatchEvent(new StorageEvent("local-storage")) } const clearStorage = () => { localStorage.removeItem("demo-key") localStorage.removeItem("user-prefs") localStorage.removeItem("counter") localStorage.removeItem("custom-key") localStorage.removeItem(inputKey) window.dispatchEvent(new StorageEvent("local-storage")) } return ( <div className="space-y-4 p-6"> <div className="flex gap-2"> <button onClick={setExampleValues} className="rounded bg-primary px-3 py-2 text-sm text-primary-foreground hover:bg-primary/90" > Set Example Data </button> <button onClick={clearStorage} className="rounded border border-input bg-background px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground" > Clear All </button> </div> <div className="space-y-3 text-sm"> <div className="grid grid-cols-[120px_1fr] gap-2 items-center p-2 bg-muted rounded"> <span className="font-medium">demo-key:</span> <span className="font-mono text-xs"> {demoValue ? `"${demoValue}"` : "null"} </span> </div> <div className="grid grid-cols-[120px_1fr] gap-2 items-center p-2 bg-muted rounded"> <span className="font-medium">user-prefs:</span> <span className="font-mono text-xs"> {userPrefs ? JSON.stringify(userPrefs) : "null"} </span> </div> <div className="grid grid-cols-[120px_1fr] gap-2 items-center p-2 bg-muted rounded"> <span className="font-medium">counter:</span> <span className="font-mono text-xs"> {count !== null ? count.toString() : "null"} </span> </div> <div className="grid grid-cols-[120px_1fr] gap-2 items-center p-2 bg-muted rounded"> <div className="space-y-1"> <input type="text" value={inputKey} onChange={(e) => setInputKey(e.target.value)} placeholder="Enter key..." className="w-full px-2 py-1 text-xs border rounded" /> </div> <span className="font-mono text-xs"> {customValue ? `"${customValue}"` : "null"} </span> </div> </div> <div className="text-xs text-muted-foreground bg-muted p-3 rounded"> <p><strong>Try this:</strong> Set example data, then open another tab with this page. Change localStorage in one tab and watch values update in both!</p> </div> </div> )}export default UseReadLocalStorageDemo
"use client"import { useCallback, useEffect, useState } from "react"import { useEventListener } from "@/components/ui/kibo-ui/use-event-listener"const IS_SERVER = typeof window === "undefined"type Options<T, InitializeWithValue extends boolean | undefined> = { deserializer?: (value: string) => T initializeWithValue: InitializeWithValue}// SSR versionexport function useReadLocalStorage<T>( key: string, options: Options<T, false>,): T | null | undefined// CSR versionexport function useReadLocalStorage<T>( key: string, options?: Partial<Options<T, true>>,): T | nullexport function useReadLocalStorage<T>( key: string, options: Partial<Options<T, boolean>> = {},): T | null | undefined { let { initializeWithValue = true } = options if (IS_SERVER) { initializeWithValue = false } const deserializer = useCallback<(value: string) => T | null>( (value) => { if (options.deserializer) { return options.deserializer(value) } // Support 'undefined' as a value if (value === "undefined") { return undefined as unknown as T } let parsed: unknown try { parsed = JSON.parse(value) } catch (error) { console.error("Error parsing JSON:", error) return null } return parsed as T }, [options], ) // Get from local storage then // parse stored json or return initialValue const readValue = useCallback((): T | null => { // Prevent build error "window is undefined" but keep keep working if (IS_SERVER) { return null } try { const raw = window.localStorage.getItem(key) return raw ? deserializer(raw) : null } catch (error) { console.warn(`Error reading localStorage key "${key}":`, error) return null } }, [key, deserializer]) const [storedValue, setStoredValue] = useState(() => { if (initializeWithValue) { return readValue() } return undefined }) // Listen if localStorage changes useEffect(() => { setStoredValue(readValue()) // eslint-disable-next-line react-hooks/exhaustive-deps }, [key]) const handleStorageChange = useCallback( (event: StorageEvent | CustomEvent) => { if ((event as StorageEvent).key && (event as StorageEvent).key !== key) { return } setStoredValue(readValue()) }, [key, readValue], ) // this only works for other documents, not the current one useEventListener("storage", handleStorageChange) // this is a custom event, triggered in writeValueToLocalStorage // See: useLocalStorage() useEventListener("local-storage", handleStorageChange) return storedValue}export type { Options }
Read-only localStorage access with SSR safety. Perfect for configuration reading and one-way data binding.
Install React Hooks via shadcn/ui CLI - Own Your Code
Install individual React hooks directly into your JavaScript, TypeScript, or Next.js project as source code:
# Install React localStorage hook - copies source code to your project
npx shadcn@latest add https://www.shadcn.io/registry/use-local-storage.json
# Install React media query hook - full source code ownership
npx shadcn@latest add https://www.shadcn.io/registry/use-media-query.json
# Install React debounce hook - customize as needed
npx shadcn@latest add https://www.shadcn.io/registry/use-debounce-value.json
Why shadcn/ui Registry? Install hooks as source code for complete control, zero dependencies, and unlimited customization. Perfect for teams who need TypeScript-first solutions with production-grade reliability.
React Hooks Code Examples - Next.js, TypeScript & JavaScript Integration
Next.js React Application with TypeScript
Build responsive Next.js applications using React hooks for state management and UI interactions:
"use client";
import { useLocalStorage, useMediaQuery, useDarkMode } from "@/hooks";
export default function App() {
// React localStorage hook for user persistence
const [user, setUser] = useLocalStorage("user", null);
// React media query hook for responsive design
const isMobile = useMediaQuery("(max-width: 768px)");
// React dark mode hook with system preference
const { isDark, toggle } = useDarkMode();
return (
<div
className={`${isMobile ? "mobile-layout" : "desktop-layout"} ${
isDark ? "dark" : ""
}`}
>
<button onClick={toggle}>Toggle Dark Mode</button>
{user ? <Dashboard user={user} /> : <Login onLogin={setUser} />}
</div>
);
}
React Hooks with shadcn/ui Components
Create interactive React components with custom hooks and shadcn/ui:
import { useBoolean, useOnClickOutside, useDebounceValue } from "@/hooks";
import { Dialog } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
export function SearchModal({ children }) {
// React boolean hook for modal state
const [isOpen, { setTrue, setFalse }] = useBoolean(false);
// React click outside hook for UX
const ref = useRef(null);
useOnClickOutside(ref, setFalse);
// React debounce hook for search optimization
const [search, setSearch] = useState("");
const debouncedSearch = useDebounceValue(search, 300);
return (
<Dialog open={isOpen} onOpenChange={setFalse}>
<div ref={ref}>
<Input
placeholder="Search..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
{/* Search results using debouncedSearch */}
{children}
</div>
</Dialog>
);
}
JavaScript (Non-TypeScript) Usage
Use React hooks in JavaScript projects without TypeScript:
import { useLocalStorage, useCounter, useInterval } from "@/hooks";
export function Counter() {
// React counter hook with localStorage persistence
const [count, { increment, decrement, reset }] = useCounter(0);
const [savedCount, setSavedCount] = useLocalStorage("counter", 0);
// React interval hook for auto-increment
useInterval(() => {
increment();
}, 1000);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
<button onClick={() => setSavedCount(count)}>Save Count</button>
</div>
);
}
Start Building with React Hooks Today
Ready to accelerate your React development? Our comprehensive React hooks library provides everything you need for modern JavaScript, TypeScript, and Next.js applications. Each hook is production-tested, performance-optimized, and designed for real-world React development.
Perfect for: React developers, JavaScript programmers, TypeScript projects, Next.js applications, frontend development, UI component libraries, and modern web development workflows.