✨ 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:
<!DOCTYPE html> <html> <head> <!-- Add Invokers via CDN (includes all commands) --> <script type="module" src="https://esm.sh/invokers/compatible"></script> </head> <body> <!-- Toggle a navigation menu with zero JavaScript --> <button type="button" command="--toggle" commandfor="nav-menu" aria-expanded="false"> Menu </button> <nav id="nav-menu" hidden> <a href="/home">Home</a> <a href="/about">About</a> <!-- Dismiss button that hides itself --> <button type="button" command="--hide" commandfor="nav-menu">✕</button> </nav> <!-- Hover cards work automatically with Interest Invokers --> <a href="/profile" interestfor="profile-hint">@username</a> <div id="profile-hint" popover="hint"> <strong>John Doe</strong><br> Software Developer<br> ? San Francisco </div> </body> </html>
That's it! No event listeners, no DOM queries, no state management. The HTML describes the behavior, and Invokers makes it work.
? Platform Proposals & Standards Alignment
Invokers is built on emerging web platform proposals from the OpenUI Community Group and WHATWG, providing a polyfill today for features that will become native browser APIs tomorrow. This section explains the underlying standards and how Invokers extends them.
HTML Invoker Commands API
The Invoker Commands API is a W3C/WHATWG proposal that introduces the command and commandfor attributes to HTML <button> elements. This allows buttons to declaratively trigger actions on other elements without JavaScript.
Core Proposal Features
commandattribute: Specifies the action to perform (e.g.,show-modal,toggle-popover)commandforattribute: References the target element by IDCommandEvent: Dispatched on the target element when the button is activated- Built-in commands: Native browser behaviors for dialogs and popovers
Example from the Specification
<button command="show-modal" commandfor="my-dialog">Open Dialog</button> <dialog id="my-dialog">Hello World</dialog>
How Invokers Extends This
Invokers provides a complete polyfill for the Invoker Commands API while adding extensive enhancements:
- Extended Command Set: Adds 50+ custom commands (
--toggle,--fetch:get,--media:play, etc.) beyond the spec's basic commands - Advanced Event Triggers: Adds
command-onattribute for any DOM event (click, input, submit, etc.) - Expression Engine: Adds
{{...}}syntax for dynamic command parameters - Command Chaining: Adds
<and-then>elements anddata-and-thenattributes for workflow orchestration - Conditional Logic: Adds success/error state handling with
data-after-success/data-after-error - Lifecycle States: Adds
once,disabled,completedstates for sophisticated interactions
Interest Invokers (Hover Cards & Tooltips)
The Interest Invokers proposal introduces the interestfor attribute for creating accessible hover cards, tooltips, and preview popovers that work across all input modalities.
Core Proposal Features
interestforattribute: Connects interactive elements to hovercard/popover content- Multi-modal Support: Works with mouse hover, keyboard focus, and touchscreen long-press
- Automatic Accessibility: Manages ARIA attributes and focus behavior
- Delay Controls: CSS properties for customizing show/hide timing
- Pseudo-classes:
:interest-sourceand:interest-targetfor styling
Example from the Specification
<a href="/profile" interestfor="user-card">@username</a> <div id="user-card" popover="hint">User details...</div>
How Invokers Extends This
Invokers includes a complete polyfill for Interest Invokers with additional enhancements:
- Extended Element Support: Works on all
HTMLElementtypes (spec currently limits to specific elements) - Touchscreen Context Menu Integration: Adds "Show Details" item to existing long-press menus
- Advanced Delay Controls: Full support for
interest-delay-start/interest-delay-endCSS properties - Pseudo-class Support: Implements
:interest-sourceand:interest-targetpseudo-classes - Combined Usage: Works seamlessly with Invoker Commands on the same elements
Popover API Integration
Invokers has deep integration with the Popover API, automatically handling popover lifecycle and accessibility when using popover attributes.
Automatic Behaviors
- Popover Commands:
toggle-popover,show-popover,hide-popoverwork natively - ARIA Management: Automatic
aria-expandedandaria-detailsattributes - Focus Management: Proper focus restoration when popovers close
- Top Layer Integration: Works with the browser's top layer stacking context
Standards Compliance & Future-Proofing
Current Browser Support
- Chrome/Edge: Full Invoker Commands support (v120+)
- Firefox: Partial support, actively developing
- Safari: Under consideration
- Polyfill Coverage: Invokers provides complete fallback for all browsers
Standards Timeline
- Invoker Commands: Graduated from OpenUI, in WHATWG HTML specification
- Interest Invokers: Active proposal, expected to graduate soon
- Popover API: Already shipping in major browsers
Migration Path
As browsers implement these features natively:
- Invokers will automatically detect native support
- Polyfill behaviors will gracefully disable
- Your HTML markup remains unchanged
- Enhanced features (chaining, expressions) continue to work
Why Invokers vs. Native-Only
While waiting for universal browser support, Invokers provides:
- Immediate Availability: Use these features today in any browser
- Enhanced Functionality: Command chaining, expressions, and advanced workflows
- Backward Compatibility: Works alongside native implementations
- Progressive Enhancement: Adds features without breaking existing code
This standards-first approach ensures your code is future-proof while providing powerful enhancements that complement the core platform proposals.
? How Does This Compare?
Invokers is designed to feel like a natural extension of HTML, focusing on client-side interactions and aligning with future web standards. Here’s how its philosophy and approach differ from other popular libraries.
| Feature | Vanilla JS | HTMX | Alpine.js | Stimulus | Invokers |
|---|---|---|---|---|---|
| Philosophy | Imperative | Hypermedia (Server-centric) | JS in HTML (Component-like) | JS Organization (MVC-like) | Declarative HTML (Browser-centric) |
| Standards-Aligned | ✅ | ❌ | ❌ | ❌ | ✅ (Core Mission) |
| Primary Use Case | Anything | Server-rendered partials | Self-contained UI components | Organizing complex JS | JS-free UI patterns & progressive enhancement |
| JS Required for UI | Always | For server comms | For component logic | Always (in controllers) | Often none for common patterns |
| Accessibility | Manual | Manual | Manual | Manual | ✅ (Automatic ARIA management) |
| Learning Curve | High | Medium (Hypermedia concepts) | Low (Vue-like syntax) | Medium (Controller concepts) | Very Low (HTML attributes) |
? vs HTMX
HTMX makes your server the star; Invokers makes your browser the star.
HTMX is a hypermedia-driven library where interactions typically involve a network request to a server, which returns HTML. Invokers is client-centric, designed to create rich UI interactions directly in the browser, often without any network requests or custom JavaScript.
Use Case: Inline Editing
A user clicks "Edit" to change a name, then "Save" or "Cancel".
HTMX: Server-Driven Swapping
HTMX replaces a div with a form fragment fetched from the server. The entire state transition is managed by server responses.
<!-- HTMX requires a server to serve the edit-form fragment --> <div id="user-1" hx-target="this" hx-swap="outerHTML"> <strong>Jane Doe</strong> <button hx-get="/edit-form/1" class="btn"> Edit </button> </div> <!-- On click, the server returns this HTML fragment: --> <!-- <form hx-put="/user/1"> <input name="name" value="Jane Doe"> <button type="submit">Save</button> <button hx-get="/user/1">Cancel</button> </form> -->
Invokers: Client-Side State Toggling (No JS, No Server)
Invokers handles this by toggling the visibility of two divs that already exist on the page. It's instantaneous and requires zero network latency or server-side logic for the UI change.
<!-- Invokers handles this entirely on the client, no server needed --> <div class="user-profile"> <!-- 1. The view state (visible by default) --> <div id="user-view"> <strong>Jane Doe</strong> <button type="button" class="btn" command="--hide" commandfor="user-view" data-and-then="--show" data-and-then-commandfor="user-edit"> Edit </button> </div> <!-- 2. The edit state (hidden by default) --> <div id="user-edit" hidden> <input type="text" value="Jane Doe"> <button type="button" class="btn-primary" command="--emit:save-user:1">Save</button> <button type="button" class="btn" command="--hide" commandfor="user-edit" data-and-then="--show" data-and-then-commandfor="user-view"> Cancel </button> </div> </div>
Use Case: Dynamic Content Swapping & Fetching
Replace page sections with new content, either from templates or remote APIs, with precise control over insertion strategy.
HTMX: Server-Driven Content Swapping
HTMX fetches HTML fragments from the server and swaps them into the DOM using hx-swap strategies.
<!-- HTMX requires server endpoints for each content type --> <div id="content-area"> <button hx-get="/api/widget-a" hx-swap="innerHTML">Load Widget A</button> <button hx-get="/api/widget-b" hx-swap="outerHTML" hx-target="#content-area">Replace Container</button> </div> <!-- Server must return complete HTML fragments -->
Invokers: Client-Side DOM Swapping & Fetching Invokers can swap content from local templates or fetch from APIs, with granular control over insertion strategies.
<!-- Templates defined in the same HTML document --> <template id="widget-a-template"> <div class="widget widget-a"> <h3>Widget A</h3> <p>This content comes from a local template.</p> </div> </template> <template id="widget-b-template"> <div class="widget widget-b"> <h3>Widget B</h3> <p>This replaces the entire container.</p> </div> </template> <div id="content-area"> <!-- Swap with local templates using different strategies --> <button command="--dom:swap" data-template-id="widget-a-template" commandfor="#content-area" data-replace-strategy="innerHTML"> Load Widget A (Inner) </button> <button command="--dom:swap" data-template-id="widget-b-template" commandfor="#content-area" data-replace-strategy="outerHTML"> Load Widget B (Replace Container) </button> <!-- Fetch remote content with precise insertion control --> <button command="--fetch:get" data-url="/api/sidebar" commandfor="#content-area" data-replace-strategy="beforeend"> Add Sidebar </button> <button command="--fetch:get" data-url="/api/header" commandfor="#content-area" data-replace-strategy="afterbegin"> Prepend Header </button> </div>
Key Differences:
- Philosophy: HTMX extends HTML as a hypermedia control. Invokers extends HTML for rich, client-side UI interactions.
- Network: HTMX is chatty by design. Invokers is silent unless you explicitly use
--fetch. - State: With HTMX, UI state often lives on the server. With Invokers, UI state lives in the DOM.
- Use Case: HTMX is excellent for server-rendered apps (Rails, Django, PHP). Invokers excels at enhancing static sites, design systems, and front-end frameworks.
? vs Alpine.js
Alpine puts JavaScript logic in your HTML; Invokers keeps it out.
Alpine.js gives you framework-like reactivity and state management by embedding JavaScript expressions in x- attributes. Invokers achieves similar results using a predefined set of commands, keeping your markup free of raw JavaScript and closer to standard HTML.
Use Case: Textarea Character Counter
Show a live character count as a user types in a textarea.
Alpine.js: State and Logic in x-data
Alpine creates a small, self-contained component with its own state (message) and uses JS properties (message.length) directly in the markup.
<!-- Alpine puts a "sprinkle" of JavaScript directly in the HTML --> <div x-data="{ message: '', limit: 140 }"> <textarea x-model="message" :maxlength="limit" class="input"></textarea> <p class="char-count"> <span x-text="message.length">0</span> / <span x-text="limit">140</span> </p> </div>
Invokers: Declarative Commands and Expressions
Invokers uses the command-on attribute to listen for the input event and the {{...}} expression engine to update the target's text content. It describes the relationship between elements, not component logic.
<!-- Invokers describes the event and action, no JS logic in the HTML --> <div> <textarea id="message-input" maxlength="140" class="input" command-on="input" command="--text:set:{{this.value.length}}" commandfor="char-count"></textarea> <p class="char-count"> <span id="char-count">0</span> / 140 </p> </div>
Key Differences:
- Syntax: Alpine uses custom JS-like attributes (
x-data,x-text). Invokers uses standard-proposal attributes (command,commandfor) and CSS-like command names (--text:set). - State: Alpine encourages creating explicit state (
x-data). Invokers derives state directly from the DOM (e.g.,this.value.length). - Paradigm: Alpine creates "mini-apps" in your DOM. Invokers creates declarative "event-action" bindings between elements.
- Future: The
commandattribute is on a standards track. Alpine's syntax is specific to the library.
? vs Stimulus
Stimulus organizes your JavaScript; Invokers helps you eliminate it.
Stimulus is a modest JavaScript framework that connects HTML to JavaScript objects (controllers). It’s designed for applications with significant custom JavaScript logic. Invokers is designed to handle common UI patterns with no custom JavaScript at all.
Use Case: Copy to Clipboard with Feedback
A user clicks a button to copy a URL to their clipboard, and the button provides feedback by changing its text to "Copied!" for a moment.
Stimulus: HTML Connected to a JS Controller
Stimulus requires a JavaScript controller to hold the logic for interacting with the clipboard API and managing the button's state (text change and timeout). The HTML contains data-* attributes to connect elements to this controller.
<!-- Stimulus connects HTML elements to a required JS controller --> <div data-controller="clipboard"> <input data-clipboard-target="source" type="text" value="https://example.com" readonly> <button data-action="clipboard#copy" class="btn"> Copy Link </button> </div>
// A "clipboard_controller.js" file is required import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["source"] copy(event) { // Logic to interact with the browser API navigator.clipboard.writeText(this.sourceTarget.value) // Custom logic for UI feedback const originalText = event.currentTarget.textContent event.currentTarget.textContent = "Copied!" setTimeout(() => { event.currentTarget.textContent = originalText }, 2000) } }
Invokers: Declarative Behavior with Command Chaining
Invokers has a built-in --clipboard:copy command. The UI feedback is handled declaratively by chaining commands in the data-and-then attribute. The entire workflow is defined in a single, readable line with no separate JavaScript file needed.
<!-- Invokers handles this with a single line of chained commands --> <div> <input id="share-url" type="text" value="https://example.com" readonly> <button type="button" class="btn" command="--clipboard:copy" commandfor="share-url" data-and-then="--text:set:Copied!, --command:delay:2000, --text:set:Copy Link"> Copy Link </button> </div>
(Note: For more robust error handling, you could use data-after-success instead of data-and-then to ensure the feedback only runs if the copy action succeeds.)
Key Differences:
- Ceremony: Stimulus requires a specific file structure and JS classes for every distinct piece of functionality. Invokers requires only HTML attributes for most tasks.
- Source of Truth: In Stimulus, the behavior logic lives in the JS controller. In Invokers, the entire workflow is declared directly in the HTML.
- Goal: Stimulus aims to give structure to complex applications that will inevitably have a lot of custom JS. Invokers aims to prevent you from needing to write JS in the first place for common UI patterns.
- When to Choose: Use Stimulus when you have complex, stateful client-side logic that needs organization. Use Invokers when you want to build interactive UIs quickly with minimal or no JavaScript boilerplate.
? Why Invokers?
Write interactive UIs without JavaScript. Invokers transforms static HTML into dynamic, interactive interfaces using declarative attributes. Perfect for progressive enhancement, component libraries, and reducing JavaScript complexity.
<!-- Toggle a menu --> <button command="--toggle" commandfor="menu">Menu</button> <nav id="menu" hidden>...</nav> <!-- Form with dynamic feedback --> <form command-on="submit.prevent" command="--fetch:send" commandfor="#result"> <input name="query" placeholder="Search..."> <button type="submit">Search</button> </form> <div id="result"></div>
? Modular Architecture
Choose exactly what you need. Invokers now features a hyper-modular architecture with four tiers:
- ?️ Tier 0: Core polyfill (25.8 kB) - Standards-compliant foundation
- ⚡ Tier 1: Essential commands (~30 kB) - Basic UI interactions
- ? Tier 2: Specialized packs (25-47 kB each) - Advanced functionality
- ? Tier 3: Reactive engine (26-42 kB) - Dynamic templating & events
? Installation & Basic Usage
Core Installation (25.8 kB)
For developers who want just the standards polyfill:
import 'invokers'; // That's it! Now command/commandfor attributes work
<!-- Native/polyfilled commands work immediately --> <button command="toggle-popover" commandfor="menu">Menu</button> <div id="menu" popover>Menu content</div>
Essential UI Commands (+30 kB)
Add the most common interactive commands:
import invokers from 'invokers'; import { registerBaseCommands } from 'invokers/commands/base'; import { registerFormCommands } from 'invokers/commands/form'; registerBaseCommands(invokers); registerFormCommands(invokers);
<!-- Now you can use essential commands --> <button command="--toggle" commandfor="sidebar">Toggle Sidebar</button> <button command="--class:toggle:dark-mode" commandfor="body">Dark Mode</button> <button command="--text:set:Hello World!" commandfor="output">Set Text</button>
?️ Command Packs
Tier 1: Essential Commands
Base Commands (invokers/commands/base) - 29.2 kB
Essential UI state management without DOM manipulation.
import { registerBaseCommands } from 'invokers/commands/base'; registerBaseCommands(invokers);
Commands: --toggle, --show, --hide, --class:*, --attr:*
<button command="--toggle" commandfor="menu">Menu</button> <button command="--class:add:active" commandfor="tab1">Activate Tab</button> <button command="--attr:set:aria-expanded:true" commandfor="dropdown">Expand</button>
Form Commands (invokers/commands/form) - 30.5 kB
Form interactions and content manipulation.
import { registerFormCommands } from 'invokers/commands/form'; registerFormCommands(invokers);
Commands: --text:*, --value:*, --focus, --disabled:*, --form:*, --input:step
<button command="--text:set:Form submitted!" commandfor="status">Submit</button> <button command="--value:set:admin@example.com" commandfor="email">Use Admin Email</button> <button command="--input:step:5" commandfor="quantity">+5</button>
Tier 2: Specialized Commands
DOM Manipulation (invokers/commands/dom) - 47.1 kB
Dynamic content insertion and templating.
import { registerDomCommands } from 'invokers/commands/dom'; registerDomCommands(invokers);
Commands: --dom:*, --template:*
<button command="--dom:append" commandfor="list" data-template-id="item-tpl">Add Item</button> <button command="--template:render:user-card" commandfor="output" data-name="John" data-email="john@example.com">Render User</button>
Flow Control (invokers/commands/flow) - 45.3 kB
Async operations, navigation, and data binding.
import { registerFlowCommands } from 'invokers/commands/flow'; registerFlowCommands(invokers);
Commands: --fetch:*, --navigate:*, --emit:*, --command:*, --bind:*
<!-- Basic fetch with replace strategies --> <button command="--fetch:get" data-url="/api/users" commandfor="user-list" data-replace-strategy="innerHTML">Load Users</button> <!-- Form submission with custom replace strategy --> <form id="contact-form" action="/api/contact" method="post"></form> <button command="--fetch:send" commandfor="contact-form" data-response-target="#response" data-replace-strategy="outerHTML">Send Message</button> <button command="--navigate:to:/dashboard">Go to Dashboard</button> <input command-on="input" command="--bind:value" data-bind-to="#output" data-bind-as="text">
Replace Strategies:
innerHTML(default): Replace target element's contentouterHTML: Replace entire target elementbeforebegin/afterbegin/beforeend/afterend: Insert adjacent to target
Media & Animation (invokers/commands/media) - 27.7 kB
Rich media controls and interactions.