
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
react-touch-outside
Advanced tools
A modern, performant React hook and component for detecting clicks/touches outside of elements. Works with React and React Native. Zero dependencies, TypeScript ready, tree-shakeable.
🚀 Ultra-lightweight (1.037KB gzipped) React hook and component for detecting clicks/touches outside of elements. Works seamlessly with React (web) and React Native. Zero dependencies, TypeScript ready, tree-shakeable.
npm install react-touch-outside
# or
yarn add react-touch-outside
# or
pnpm add react-touch-outside
Replacing other click-outside libraries? It's easy:
// Before (react-click-outside)
import { useClickOutside } from 'react-click-outside'
// After (react-touch-outside) - Same API!
import { useTouchOutside } from 'react-touch-outside'
import { useTouchOutside } from 'react-touch-outside'
function MyModal() {
const { ref, isOutside } = useTouchOutside((event) => {
console.log('Clicked outside the modal!')
// Close modal, hide dropdown, etc.
})
return (
<div ref={ref} className="modal">
<h2>Modal Content</h2>
<p>Click outside to close</p>
{isOutside && <span>Outside clicked!</span>}
</div>
)
}
import { TouchOutside } from 'react-touch-outside'
function Dropdown() {
const [isOpen, setIsOpen] = useState(false)
return (
<TouchOutside
onOutside={() => setIsOpen(false)}
className="dropdown-wrapper"
>
<button onClick={() => setIsOpen(!isOpen)}>
Toggle Dropdown
</button>
{isOpen && (
<div className="dropdown-menu">
<a href="#">Option 1</a>
<a href="#">Option 2</a>
</div>
)}
</TouchOutside>
)
}
useTouchOutside(callback, options?)The main hook for detecting outside interactions.
callback (event: Event) => void - Function called when outside interaction is detectedoptions TouchOutsideOptions - Configuration options (optional){
ref: RefObject<HTMLElement> // Ref to attach to your element
isInside: boolean // Whether last interaction was inside
isOutside: boolean // Whether last interaction was outside
}
interface TouchOutsideOptions {
enabled?: boolean // Whether the hook is active (default: true)
eventType?: 'click' | 'touchstart' | 'mousedown' // Event to listen for
capture?: boolean // Use capture phase (default: false)
stopPropagation?: boolean // Stop event propagation (default: false)
onOutside?: (event: Event) => void // Called when outside is detected
onInside?: (event: Event) => void // Called when inside is detected
debounceMs?: number // Debounce delay in milliseconds (default: 0)
}
TouchOutside ComponentA declarative wrapper component for outside detection.
interface TouchOutsideProps extends TouchOutsideOptions {
children: React.ReactNode // Content to wrap
className?: string // CSS class name
style?: React.CSSProperties // Inline styles
as?: keyof JSX.IntrinsicElements // HTML element to render (default: 'div')
wrapperProps?: Record<string, any> // Additional props for wrapper
}
function AdvancedModal({ isOpen, onClose }) {
const { ref } = useTouchOutside(() => onClose(), {
enabled: isOpen,
stopPropagation: true
})
useEffect(() => {
const handleEscape = (e) => {
if (e.key === 'Escape') onClose()
}
if (isOpen) {
document.addEventListener('keydown', handleEscape)
return () => document.removeEventListener('keydown', handleEscape)
}
}, [isOpen, onClose])
if (!isOpen) return null
return (
<div className="modal-overlay">
<div ref={ref} className="modal">
<button onClick={onClose} className="close-btn">×</button>
<h2>Advanced Modal</h2>
<p>Click outside or press Escape to close</p>
</div>
</div>
)
}
function MultiLevelDropdown() {
const [activeLevel, setActiveLevel] = useState(null)
const { ref } = useTouchOutside(() => {
setActiveLevel(null)
}, {
debounceMs: 100 // Prevent rapid toggles
})
return (
<TouchOutside
ref={ref}
onOutside={() => setActiveLevel(null)}
className="dropdown-container"
>
<div className="dropdown-level-1">
<button onClick={() => setActiveLevel(1)}>
Level 1
</button>
{activeLevel === 1 && (
<div className="dropdown-level-2">
<button onClick={() => setActiveLevel(2)}>
Level 2
</button>
{activeLevel === 2 && (
<div className="dropdown-content">
<a href="#">Option A</a>
<a href="#">Option B</a>
</div>
)}
</div>
)}
</div>
</TouchOutside>
)
}
import { useTouchOutside } from 'react-touch-outside'
function MobileModal() {
const { ref, isOutside } = useTouchOutside((event) => {
// Handle outside touch on mobile
console.log('Touched outside modal')
}, {
eventType: 'touchstart' // Use touch events for React Native
})
return (
<View ref={ref} style={styles.modal}>
<Text>Modal Content</Text>
{isOutside && <Text>Outside touched!</Text>}
</View>
)
}
function VirtualizedList() {
const { ref } = useTouchOutside(() => {
// Close dropdown when scrolling outside
}, {
debounceMs: 50, // Reduce event frequency
capture: true // Capture during capture phase for better performance
})
return (
<div ref={ref} className="virtual-list">
{/* Virtual list content */}
</div>
)
}
// ✅ Good: Use hook for complex interactions
const { ref } = useTouchOutside((event) => {
if (event.target.closest('.keep-open')) return
onClose()
})
// ❌ Avoid: Over-engineering simple cases
<TouchOutside onOutside={onClose}>
<ComplexComponent />
</TouchOutside>
// ✅ Good: Use debouncing for performance
const { ref } = useTouchOutside(onClose, {
debounceMs: 100
})
// ✅ Good: Disable when not needed
const { ref } = useTouchOutside(onClose, {
enabled: isOpen
})
// ✅ Good: Check for valid targets
const { ref } = useTouchOutside((event) => {
if (!event.target || !document.contains(event.target)) return
onClose()
})
// ✅ Good: Combine with keyboard navigation
const { ref } = useTouchOutside(onClose)
useEffect(() => {
const handleEscape = (e) => {
if (e.key === 'Escape') onClose()
}
document.addEventListener('keydown', handleEscape)
return () => document.removeEventListener('keydown', handleEscape)
}, [onClose])
The package automatically detects your environment:
click and touchstart eventstouchstart events// For web applications
const { ref } = useTouchOutside(callback, {
eventType: 'mousedown' // More responsive than 'click'
})
// For React Native
const { ref } = useTouchOutside(callback, {
eventType: 'touchstart'
})
This package is optimized for minimal bundle impact:
// Import only the hook (smallest bundle)
import { useTouchOutside } from 'react-touch-outside'
// Import everything (still small!)
import { useTouchOutside, TouchOutside } from 'react-touch-outside'
The package includes comprehensive tests. For testing your components:
import { render, fireEvent } from '@testing-library/react'
import { useTouchOutside } from 'react-touch-outside'
test('should detect outside clicks', () => {
const onOutside = jest.fn()
const { container } = render(
<div>
<div data-testid="inside">Inside</div>
<div data-testid="outside">Outside</div>
</div>
)
fireEvent.click(container.querySelector('[data-testid="outside"]'))
expect(onOutside).toHaveBeenCalled()
})
Contributions are welcome! Please read our Contributing Guide for details.
git clone https://github.com/ytahirkose/react-touch-outside.git
cd react-touch-outside
npm install
npm run dev
npm run dev - Start development servernpm run build - Build for productionnpm run test - Run testsnpm run test:coverage - Run tests with coveragenpm run type-check - TypeScript type checkingnpm run lint - ESLint checkingMIT © Yaşar Tahir Köse
// Shopping cart popover
const { ref } = useTouchOutside(() => setCartOpen(false))
// Product quick view modal
const { ref } = useTouchOutside(() => setQuickViewOpen(false))
// User profile dropdown
const { ref } = useTouchOutside(() => setProfileOpen(false))
// Settings panel
const { ref } = useTouchOutside(() => setSettingsOpen(false))
// Bottom sheet
const { ref } = useTouchOutside(() => setBottomSheetOpen(false))
// Action sheet
const { ref } = useTouchOutside(() => setActionSheetOpen(false))
| Feature | react-touch-outside | react-click-outside | react-outside-click-handler |
|---|---|---|---|
| Bundle Size | 1.037KB gzipped | ~2KB+ | ~3KB+ |
| React Native | ✅ Native support | ❌ Web only | ❌ Web only |
| TypeScript | ✅ Full support | ⚠️ Partial | ⚠️ Partial |
| Tree Shaking | ✅ Optimized | ❌ Limited | ❌ Limited |
| Zero Dependencies | ✅ Yes | ❌ No | ❌ No |
| Modern API | ✅ Hooks + Components | ⚠️ HOC only | ⚠️ HOC only |
const { ref } = useTouchOutside(callback, { debounceMs: 100 })
const { ref } = useTouchOutside(callback, { enabled: isOpen })
const { ref } = useTouchOutside(callback, { capture: true })
Want to see the exact impact on your bundle? Check out Bundlephobia for detailed analysis.
We welcome contributions! Please see our Contributing Guide for details.
npm run dev # Start development server
npm run build # Build for production
npm run test # Run tests
npm run lint # Run ESLint
npm run type-check # TypeScript type checking
MIT © Yaşar Tahir Köse
Made with ❤️ for the React community
⭐ Star this repo | 🐛 Report an issue | 💡 Request a feature | 📖 Documentation
FAQs
A modern, performant React hook and component for detecting clicks/touches outside of elements. Works with React and React Native. Zero dependencies, TypeScript ready, tree-shakeable.
We found that react-touch-outside demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.