Lightweight library to block user interactions in browsers.
- Factory-based API: Support for both global and element-specific locks
- No overlay elements: Blocks interactions without adding elements to the DOM
- Scope filtering: Control which events to block (
inside,outside,self) - Per-lock timeout: Optional automatic unlock after specified time
- TypeScript: Full type support included
- React Hook: Built-in
useBlokr()hook for React components
Blokr provides a unique approach to blocking user interactions. Here's how it compares with other techniques:
The HTML5 inert attribute marks an element as "inert," preventing user interactions including keyboard navigation.
CSS pointer-events: none disables mouse and touch events on elements, but cannot block keyboard events or prevent tab navigation.
The HTML5 <dialog> element creates a modal dialog but adds a DOM element and provides limited scope flexibility for non-modal use cases.
| Feature | Blokr | inert | pointer-events | dialog |
|---|---|---|---|---|
| Blocks keyboard events | ✅ | ✅ | ❌ | ✅ |
| Global interaction lock | ✅ | ❌ | ❌ | ❌ |
| Inside/outside scope | ✅ | ❌ | ❌ | ❌ |
| Timeout protection | ✅ | ❌ | ❌ | ❌ |
| No DOM overlay | ✅ | ✅ | ✅ | ❌ |
| No DOM modifications | ✅ | ❌ | ✅ | ❌ |
Key differentiators:
- Global interaction lock: Blokr can block interactions across the entire page, not just within specific elements
- Inside/outside scope: Unique ability to selectively block events inside or outside a target element
- Timeout protection: Automatic unlock prevents permanent locks due to errors or forgotten cleanup
- No DOM modifications: Works purely via event listeners without modifying DOM structure or attributes
- React Hook support: New
useBlokr()hook for React applications (React 18+ required)
- UMD format removed: CDN usage now requires ES modules only (
blokr/dist/index.js) - No breaking changes to core API: All v0.3.0 JavaScript APIs remain unchanged
For changes from v0.2.x, see the Migration from v0.2.x section below.
Note: This library is under active development. Future versions may introduce additional breaking changes. Please refer to the changelog before upgrading.
npm install blokrThe useBlokr() React Hook is included in the same package. React 18.0+ or React 19.0+ is required to use the hook:
npm install blokr reactThe react package is an optional peer dependency. If you don't use React, you can ignore this requirement.
import blokr from 'blokr';
// Global lock - blocks all user interactions
const instance = blokr();
instance.lock();
// Check if locked
if (instance.isLocked()) {
console.log('User interactions are blocked');
}
// Unlock
instance.unlock();import blokr from 'blokr';
const container = document.querySelector('.container');
const instance = blokr(container);
// Block events inside the container (default scope)
instance.lock();
// Or explicitly specify scope
instance.lock({ scope: 'inside' }); // Block events inside container
instance.lock({ scope: 'outside' }); // Block events outside container
instance.lock({ scope: 'self' }); // Block events on the container onlyimport blokr from 'blokr';
const instance = blokr();
// Auto-unlock after 5 seconds
instance.lock({ timeout: 5000 });
// Disable timeout (lock indefinitely)
instance.lock({ timeout: 0 });<script type="module">
import blokr from 'https://unpkg.com/blokr/dist/index.js';
const instance = blokr();
instance.lock({ timeout: 3000 });
</script>Returns a Blokr instance. If no target is specified, creates a global instance that blocks all events. If the same target is provided multiple times, returns the cached instance.
Parameters:
target(optional): DOM element to scope the lock to
Returns: BlokrInstance
Examples:
// Global instance (blocks all events)
const global = blokr();
// Element-specific instance
const container = document.querySelector('.modal');
const modal = blokr(container);
// Same element returns same instance
const modal2 = blokr(container);
console.log(modal === modal2); // trueLocks user interactions. Returns true if lock was applied, false if already locked.
Parameters:
options.timeout(optional): Auto-unlock timeout in milliseconds. Default:0(no timeout)options.scope(optional): Event blocking scope. Default:'inside''inside': Block events inside the target element (default)'outside': Block events outside the target element'self': Block events on the target element only
Returns: true if lock was applied, false if already locked
Examples:
const instance = blokr();
// Basic lock
instance.lock(); // Returns true
// Already locked
instance.lock(); // Returns false
// Lock with timeout
instance.lock({ timeout: 5000 });
// Lock with scope (requires target element)
const container = document.querySelector('.panel');
const panelInstance = blokr(container);
panelInstance.lock({ scope: 'inside' });Unlocks user interactions and clears any pending timeout. Safe to call even when not locked.
Examples:
const instance = blokr();
instance.lock();
instance.unlock();
// Safe to call multiple times
instance.unlock();
instance.unlock();Returns true if user interactions are currently locked.
Returns: boolean
Examples:
const instance = blokr();
console.log(instance.isLocked()); // false
instance.lock();
console.log(instance.isLocked()); // true
instance.unlock();
console.log(instance.isLocked()); // falseimport blokr from 'blokr';
async function saveUserProfile(formData: FormData) {
const instance = blokr();
// Block all interactions with 10-second timeout
instance.lock({ timeout: 10000 });
try {
const response = await fetch('/api/profile', {
method: 'POST',
body: formData
});
if (response.ok) {
showSuccessMessage();
}
} finally {
instance.unlock();
}
}import blokr from 'blokr';
function openModal() {
const modal = document.querySelector('.modal');
const instance = blokr(modal);
modal.classList.add('visible');
// Block all interactions outside the modal
instance.lock({ scope: 'outside' });
}
function closeModal() {
const modal = document.querySelector('.modal');
const instance = blokr(modal);
modal.classList.remove('visible');
instance.unlock();
}import blokr from 'blokr';
function disableFormPanel() {
const panel = document.querySelector('.settings-panel');
const instance = blokr(panel);
// Disable interactions only inside the panel
instance.lock({ scope: 'inside' });
}
function enableFormPanel() {
const panel = document.querySelector('.settings-panel');
const instance = blokr(panel);
instance.unlock();
}import blokr from 'blokr';
async function loadData() {
const instance = blokr();
// No overlay element needed!
instance.lock({ timeout: 30000 });
try {
const data = await fetch('/api/data').then(r => r.json());
renderData(data);
} finally {
instance.unlock();
}
}The useBlokr() hook provides a React-friendly way to manage user interaction blocking. It works seamlessly with the factory-based API and manages refs automatically.
import { useBlokr } from 'blokr/react';import { useBlokr } from 'blokr/react';
export function PageWithLinks() {
const { target, lock, unlock, isLocked } = useBlokr<HTMLDivElement>();
const handleLock = () => {
lock({ timeout: 5000 }); // Auto-unlock after 5 seconds
};
return (
<>
<div ref={target}>
<a href="/page1">Go to Page 1</a>
</div>
<button onClick={handleLock}>Lock Link</button>
<button onClick={unlock}>Unlock</button>
</>
);
}The lock() function accepts the same options as the core API:
const { target, lock, unlock } = useBlokr<HTMLDivElement>();
// With timeout (auto-unlock after 5 seconds)
lock({ timeout: 5000 });
// With scope
lock({ scope: 'inside' }); // Block inside the element
lock({ scope: 'outside' }); // Block outside the element
lock({ scope: 'self' }); // Block on the element only
// With both options
lock({ scope: 'inside', timeout: 5000 });useBlokr<T = Element>(allowGlobal?: boolean): { target: RefObject<T | null>; lock: (options?: Options) => boolean; unlock: () => void; isLocked: () => boolean }
Returns an object containing a ref and three control functions for managing user interaction blocking.
Type Parameters:
T(optional): The DOM element type. Default:Element
Parameters:
allowGlobal(optional): Iftrue, enables global lock mode that blocks interactions across the entire page instead of a specific element. When using global lock, thetargetref is not needed. Default:false
Returns: An object with:
target: A React ref to assign to the target element (RefObject<T | null>)lock: Function to lock user interactions on the element ((options?: Options) => boolean)unlock: Function to unlock user interactions (() => void)isLocked: Function to check if currently locked (() => boolean)
Parameters (lock function):
options.timeout(optional): Auto-unlock timeout in millisecondsoptions.scope(optional): Event blocking scope ('inside','outside', or'self')
Returns (lock function): true if lock was applied, false if already locked or if the ref is not set (when using element-specific lock)
The allowGlobal parameter enables global lock mode, which blocks user interactions across the entire page instead of scoping to a specific element.
Global Lock (allowGlobal=true):
// No need to destructure 'target' since we're not using element-specific locking
const { lock, unlock, isLocked } = useBlokr(true);
// Locks all interactions across the entire page
lock(); // Blocks all user interactions globallyElement-Specific Lock (Default: allowGlobal=false):
const { target, lock, unlock, isLocked } = useBlokr<HTMLDivElement>();
// Attach target to an element
<div ref={target}>Content</div>
// Lock only affects this specific element (by default, scope='inside')
lock(); // Blocks interactions inside the div| v0.2.x | v0.3.0 |
|---|---|
blokr.lock() |
blokr().lock() |
blokr.unlock() |
blokr().unlock() |
blokr.unlock(true) |
blokr().unlock() (always immediate) |
blokr.setTimeout(ms) |
blokr().lock({ timeout: ms }) |
blokr.isLocked() |
blokr().isLocked() |
window.Blokr (UMD) |
window.blokr (UMD) |
In v0.2.x, multiple lock() calls incremented a counter:
// v0.2.x
blokr.lock(); // Count: 1
blokr.lock(); // Count: 2
blokr.unlock(); // Count: 1 (still locked)
blokr.unlock(); // Count: 0 (unlocked)In v0.3.0, lock() returns false if already locked:
// v0.3.0
const instance = blokr();
instance.lock(); // Returns true
instance.lock(); // Returns false (already locked)
instance.unlock(); // Unlocked// v0.3.0 only - new feature not available in v0.2.x
const container = document.querySelector('.container');
const instance = blokr(container);
// Block events inside container
instance.lock({ scope: 'inside' });
// Block events outside container
instance.lock({ scope: 'outside' });
// Block events on container itself only
instance.lock({ scope: 'self' });- Event listener priority: Event listeners are registered at the capture phase. May not work correctly when used with event delegation libraries.
MIT