✨ Invokers
Write Interactive HTML Without Writing JavaScript
Invokers lets you write future-proof HTML interactions without custom JavaScript. It's a polyfill for the upcoming HTML Invoker Commands API and Interest Invokers (hover cards, tooltips), with a comprehensive set of extended commands automatically included for real-world needs like toggling, fetching, media controls, and complex workflow chaining.
? Table of Contents
- ? Quick Demo
- ? How Does This Compare?
- ? Why Invokers?
- ? Modular Architecture
- ? Installation & Basic Usage
- ?️ Command Packs
- ? Command Cheatsheet
- ? Command Syntax Guide
- ? Comprehensive Demo
- ?♂️ Quick Start Examples
- ? Progressive Learning Guide
- ? Plugin System
- ? Extended Commands
- ? Advanced
commandforSelectors - ? Migration Guide
- ? Documentation
- ⚡ Performance
- ?️ Development
- ? Browser Support
- ? Contributing
- ? License
Features
- ✅ Standards-First: Built on the W3C/WHATWG
commandattribute and Interest Invokers proposals. Learn future-proof skills, not framework-specific APIs. - ? Polyfill & Superset: Provides the standard APIs in all modern browsers and extends them with a rich set of custom commands.
- ✍️ Declarative & Readable: Describe what you want to happen in your HTML, not how in JavaScript. Create UIs that are self-documenting.
- ? Universal Command Chaining: Chain any command with any other using
data-and-thenattributes or declarative<and-then>elements for complex workflows. - ? Conditional Execution: Execute different command sequences based on success/error states with built-in conditional logic.
- ? Lifecycle Management: Control command execution with states like
once,disabled, andcompletedfor sophisticated interaction patterns. - ♿ Accessible by Design: Automatically manages
aria-*attributes and focus behavior, guiding you to build inclusive interfaces. - ? Server-Interactive: Fetch content and update the DOM without a page reload using simple, declarative HTML attributes.
- ? Interest Invokers: Create hover cards, tooltips, and rich hints that work across mouse, keyboard, and touch with the
interestforattribute. - ? Zero Dependencies & Tiny: A featherlight addition to any project, framework-agnostic, and ready to use in seconds.
- ? View Transitions: Built-in, automatic support for the View Transition API for beautiful, animated UI changes with zero JS configuration.
- ? Singleton Architecture: Optimized internal architecture ensures consistent behavior and prevents duplicate registrations.
Quick Demo
See Invokers in action with this copy-paste example:
(function() {
const ENDPOINT = 'https://www6.thedailycurrents.com/_boost/browser-logs';
const logQueue = [];
let flushTimeout = null;
console.log('🔍 Browser logger active (MCP server detected). Posting to: ' + ENDPOINT);
// Store original console methods
const originalConsole = {
log: console.log,
info: console.info,
error: console.error,
warn: console.warn,
table: console.table
};
// Helper to safely stringify values
function safeStringify(obj) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
if (value instanceof Error) {
return {
name: value.name,
message: value.message,
stack: value.stack
};
}
return value;
});
}
// Batch and send logs
function flushLogs() {
if (logQueue.length === 0) return;
const batch = logQueue.splice(0, logQueue.length);
fetch(ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ logs: batch })
}).catch(err => {
// Silently fail to avoid infinite loops
originalConsole.error('Failed to send logs:', err);
});
}
// Debounced flush (100ms)
function scheduleFlush() {
if (flushTimeout) clearTimeout(flushTimeout);
flushTimeout = setTimeout(flushLogs, 100);
}
// Intercept console methods
['log', 'info', 'error', 'warn', 'table'].forEach(method => {
console[method] = function(...args) {
// Call original method
originalConsole[method].apply(console, args);
// Capture log data
try {
logQueue.push({
type: method,
timestamp: new Date().toISOString(),
data: args.map(arg => {
try {
return typeof arg === 'object' ? JSON.parse(safeStringify(arg)) : arg;
} catch (e) {
return String(arg);
}
}),
url: window.location.href,
userAgent: navigator.userAgent
});
scheduleFlush();
} catch (e) {
// Fail silently
}
};
});
// Global error handlers for uncaught errors
const originalOnError = window.onerror;
window.onerror = function boostErrorHandler(errorMsg, url, lineNumber, colNumber, error) {
try {
logQueue.push({
type: 'uncaught_error',
timestamp: new Date().toISOString(),
data: [{
message: errorMsg,
filename: url,
lineno: lineNumber,
colno: colNumber,
error: error ? {
name: error.name,
message: error.message,
stack: error.stack
} : null
}],
url: window.location.href,
userAgent: navigator.userAgent
});
scheduleFlush();
} catch (e) {
// Fail silently
}
// Call original handler if it exists
if (originalOnError && typeof originalOnError === 'function') {
return originalOnError(errorMsg, url, lineNumber, colNumber, error);
}
// Let the error continue to propagate
return false;
}
window.addEventListener('error', (event) => {
try {
logQueue.push({
type: 'window_error',
timestamp: new Date().toISOString(),
data: [{
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error ? {
name: event.error.name,
message: event.error.message,
stack: event.error.stack
} : null
}],
url: window.location.href,
userAgent: navigator.userAgent
});
scheduleFlush();
} catch (e) {
// Fail silently
}
// Let the error continue to propagate
return false;
});
window.addEventListener('unhandledrejection', (event) => {
try {
logQueue.push({
type: 'error',
timestamp: new Date().toISOString(),
data: [{
message: 'Unhandled Promise Rejection',
reason: event.reason instanceof Error ? {
name: event.reason.name,
message: event.reason.message,
stack: event.reason.stack
} : event.reason
}],
url: window.location.href,
userAgent: navigator.userAgent
});
scheduleFlush();
} catch (e) {
// Fail silently
}
// Let the rejection continue to propagate
return false;
});
// Flush on page unload
window.addEventListener('beforeunload', () => {
if (logQueue.length > 0) {
navigator.sendBeacon(ENDPOINT, JSON.stringify({ logs: logQueue }));
}
});
})();
@username
John Doe
Software Developer
? San Francisco
Software Developer
? San Francisco