Lightweight library to block user interactions in browsers.
Version 0.3.0 introduces significant API changes from v0.2.x:
- Factory function instead of singleton:
blokr()returns an instance instead of being a singleton object - Options-based API:
lock({ timeout, scope })instead of separatesetTimeout()method - No reference counting: Multiple
lock()calls returnfalseinstead of incrementing a counter - No
setTimeout()method: Uselock({ timeout })option instead - No
unlock(abort)parameter:unlock()always releases the lock immediately
Migration guide: See Migration from v0.2.x below.
Note: This library is under active development. Future versions may introduce additional breaking changes. Please refer to the changelog before upgrading.
- Factory-based API: Support for both global and element-specific locks
- Scope filtering: Control which events to block (
inside,outside,self) - No overlay elements: Blocks interactions without adding elements to the DOM
- All interaction types: Blocks mouse, keyboard, touch, and wheel events
- Per-lock timeout: Optional automatic unlock after specified time
- No dependencies: Zero external dependencies
- TypeScript: Full type support included
While CSS pointer-events: none can disable interactions, it has several limitations:
- Cannot block keyboard events: Tab navigation and keyboard shortcuts still work
- No timeout protection: No automatic unlock if code fails to re-enable interactions
- Requires DOM manipulation: Must add/remove CSS classes or inline styles
- Cannot scope events: Cannot selectively block events inside/outside an element
- z-index issues: Overlay approaches require careful z-index management
- ✅ Blocks all interaction types: Mouse, keyboard, touch, and wheel events
- ✅ Optional timeout protection: Automatically unlock after specified time
- ✅ No DOM changes: Works via event listeners only
- ✅ Flexible scoping: Block events inside, outside, or only on specific elements
- ✅ No z-index conflicts: No overlay elements needed
- ✅ TypeScript support: Full type definitions included
npm install blokrimport 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 container itself 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 src="https://unpkg.com/blokr/dist/blokr.js"></script>
<script>
// Note: global name is 'blokr' (lowercase) in v0.3.0
const instance = window.blokr();
instance.lock();
setTimeout(() => {
instance.unlock();
}, 3000);
</script><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 target element (default)'outside': Block events outside target element'self': Block events on target element itself 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();
}
}| 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' });- Only blocks genuine user interactions: Programmatically triggered events (e.g.,
element.click()) are not blocked. - Event listener priority: Event listeners are registered at the capture phase. May not work correctly when used with event delegation libraries. Loading Blokr before other libraries may resolve this issue.
- Target-specific locks accept Elements only: The
blokr(target)factory function only accepts DOMElementnodes. To block interactions across the entire page, use the global lock:blokr()(without a target parameter).
MIT