npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

hx-optimistic

v1.0.11

Published

HTMX extension for optimistic UI updates with automatic rollback on errors

Downloads

64

Readme

hx-optimistic

An htmx extension for optimistic UI updates with automatic rollback on errors. Combine it with speculation rules (or the htmx preload extension) and the View Transitions API for truly app‑like experience with minimal JavaScript. We love JavaScript, but use it like a spice: a pinch delights, too much overwhelms.

✨ Features

  • 🎯 Optimistic Updates - Immediate UI feedback while requests are processing
  • 🔄 Automatic Rollback - Intelligent revert to original state on errors
  • 📝 Input Interpolation - Dynamic templates with ${this.value}, ${textarea}, ${data:key} helpers
  • 🎨 Template Support - Rich HTML templates for loading and error states
  • ⚠️ Developer Warnings - Console warnings for unsupported patterns
  • 🚫 No CSS Required - You control all styling through provided class names
  • 📦 Tiny - Only 18.2KB uncompressed, 8.4KB minified, 3.0KB gzipped
  • 🔧 Highly Configurable - Fine-tune behavior per element

🚀 Quick Start

Installation

Via CDN (jsDelivr, pinned major):

<script defer src="https://unpkg.com/htmx.org@2"></script>
<script defer src="https://cdn.jsdelivr.net/npm/hx-optimistic@1/hx-optimistic.min.js"></script>

Alternative (unpkg, latest v1):

<script defer src="https://unpkg.com/htmx.org@2"></script>
<script defer src="https://unpkg.com/hx-optimistic@1/hx-optimistic.min.js"></script>

Compatibility: Works with htmx 1.9+ and 2.x.

Via NPM:

npm install hx-optimistic

Basic Usage

Enable the extension and add optimistic behavior to any HTMX element:

<body hx-ext="optimistic">
  <!-- Simple like button with optimistic updates -->
  <button
    hx-post="/api/like"
    hx-target="this"
    hx-swap="outerHTML"
    data-optimistic='{"values":{"textContent":"❤️ Liked!","className":"btn liked"},"errorMessage":"Failed to like"}'
  >
    🤍 Like
  </button>
</body>

🎯 Core Concepts

  • Use data-optimistic to declare behavior per element
  • Choose between values (simple property changes) and template (full HTML)
  • Automatic snapshot and revert: innerHTML, className, attributes, and dataset
  • Token-based concurrency prevents stale errors from overwriting newer states
  • Target resolution via hx-target or config target chains: closest, find, next, previous
  • Errors via errorMessage or errorTemplate with errorMode and delay
  • Interpolation supports safe patterns only; avoid arbitrary JS expressions

📦 Bundle Size

| Artifact | Size | |---------|------| | Unminified (hx-optimistic.js) | 18.2 KB | | Minified (hx-optimistic.min.js) | 8.4 KB | | Minified + gzip | 3.0 KB |

Values vs Templates

Values - Perfect for simple optimistic updates:

<button 
  data-count="42" 
  data-liked="false"
  hx-post="/api/like"
  hx-target="this" 
  hx-swap="outerHTML"
  data-optimistic='{
    "values": {
      "textContent": "❤️ Liked! (was ${data:count})",
      "className": "btn liked",
      "data-liked": "true"
    }
  }'>
  🤍 Like (42)
</button>

Templates - Ideal for complex optimistic UI changes:

<form hx-post="/api/comments" hx-target=".comments" hx-swap="beforeend"
      data-optimistic='{
        "template": "<div class='comment optimistic'><strong>You:</strong> ${textarea}</div>",
        "errorTemplate": "<div class='error'>❌ Comment failed to post</div>"
      }'>
  <textarea name="comment" placeholder="Your comment here"></textarea>
  <button type="submit">Post Comment</button>
</form>

Input Interpolation

Dynamic content using ${...} syntax with powerful helpers:

<form hx-post="/api/comments" hx-ext="optimistic"
      data-optimistic='{"template":"<div>Posting: ${textarea}</div>"}'>
  <textarea name="comment">Your comment here</textarea>
  <button type="submit">Post</button>
</form>

📖 Interpolation Reference

All ${...} patterns supported in templates and values:

| Pattern | Description | Example | |---------|-------------|---------| | ${this.value} | Element's input value | "Saving: ${this.value}" | | ${this.textContent} | Element's text content | "Was: ${this.textContent}" | | ${this.dataset.key} | Data attribute via dataset | "ID: ${this.dataset.userId}" | | ${textarea} | First textarea in form | "Comment: ${textarea}" | | ${email} | First email input | "Email: ${email}" | | ${data:key} | Data attribute shorthand | "Count: ${data:count}" | | ${attr:name} | Any HTML attribute | "ID: ${attr:id}" | | ${contextKey} | Value from config.context (templates only) | "Hello, ${username}" | | ${status} | HTTP status (errors only) | "Error ${status}" | | ${statusText} | HTTP status text (errors only) | "Error: ${statusText}" | | ${error} | Error message (errors only) | "Failed: ${error}" |

Form Field Helpers:

  • ${textarea}, ${email}, ${password}, ${text}, ${url}, ${tel}, ${search}
  • ${fieldName} - Any field with name="fieldName"

⚙️ Configuration Options

Complete configuration reference for data-optimistic:

Snapshot Behavior

innerHTML, className, all attributes, and the element dataset are automatically captured and restored on revert; no configuration is required.

Optimistic Updates

{
  // Simple property updates
  "values": {
    "textContent": "Loading...",
    "className": "btn loading"
  },
  
  // Rich HTML templates
  "template": "#loading-template",  // Or inline HTML
  "target": "closest .card",       // Different target for optimistic update
  "swap": "beforeend",             // Append instead of replace
  "class": "my-optimistic"         // Optional custom class applied during optimistic state
}

swap supports beforeend and afterbegin. If omitted, content is replaced.

Error Handling

{
  "errorMessage": "Request failed",
  "errorTemplate": "<div class='error'>Error ${status}: ${statusText}</div>",
  "errorMode": "append",  // "replace" (default) or "append"
  "delay": 2000          // Auto-revert delay in ms
}

Context Data

Provide additional variables for template interpolation:

{
  "template": "<div>Hello, ${username}</div>",
  "context": { "username": "Alice" }
}

Full example:

<div class="profile-card">
  <h3>Alex</h3>
  <p>Status: 🔴 Offline</p>
  <button
    hx-post="/api/status"
    hx-target="closest .profile-card"
    hx-swap="outerHTML"
    hx-ext="optimistic"
    data-optimistic='{"template":"#profile-next","errorTemplate":"#profile-error","errorMode":"append","context":{"username":"Alex","nextStatus":"Online","nextIcon":"🟢"}}'
  >
    Change Status
  </button>
</div>

<template id="profile-next">
  <div class="profile-card">
    <h3>${username}</h3>
    <p>Status: ${nextIcon} ${nextStatus}</p>
  </div>
</template>

<template id="profile-error">
  <div class="error">❌ ${error}</div>
</template>

🎨 CSS Classes

This library does not include any CSS. These classes are applied so you can style them as you wish:

  • hx-optimistic: applied during the optimistic update
  • hx-optimistic-error: applied when an error is shown
  • hx-optimistic-reverting: applied while reverting to the snapshot
  • hx-optimistic-error-message: wrapper added when errorMode is "append"
  • hx-optimistic-pending: may be applied to <button> when no values/template are provided

✅ Best Practices

  • Enable globally when possible: Add hx-ext="optimistic" on <body> so elements inherit it. Use per-element hx-ext only when you need to opt-in selectively.
  • Pick the right technique:
    • values: simple property changes (textContent, className, data-*).
    • template: richer markup; prefer a <template id="..."> and reference it with "#id".
  • Keep interpolation simple: Only supported patterns are documented (e.g., ${this.value}, ${textarea}, ${data:key}, ${attr:name}). Avoid expressions like ${count + 1}; use data-*/hx-vals to pass values.
  • Design error UX: Provide errorMessage or errorTemplate. Use errorMode: "append" to preserve content; set delay (ms) for auto-revert, or delay: 0 to keep the error state.
  • Target resolution: Use hx-target or config target with chains like closest .card find .title. Supported ops: closest, find, next, previous. Prefer stable selectors over brittle DOM traversal.
  • Style the states: Add styles for hx-optimistic, hx-optimistic-error, hx-optimistic-reverting, and hx-optimistic-error-message, or provide a custom class in config.
  • Concurrency is automatic: Overlapping requests are tokenized; older errors won’t clobber newer optimistic states. Avoid writing concurrency flags into dataset.
  • Snapshots are automatic: innerHTML, className, attributes, and dataset are restored automatically on revert; no custom snapshot configuration is needed.
  • Pass extra data via context: Use context to provide additional variables to templates. Error templates also receive ${status}, ${statusText}, and ${error}.
  • Accessibility: The extension preserves focus within the target after error/revert. Ensure visible focus styles and consider ARIA live regions for error messages.
  • Diagnostics: Watch the console for warnings about unresolved selectors/templates or unsupported interpolation patterns, and fix the sources accordingly.
  • Default button behavior: If data-optimistic is present on a <button> without values or template, a hx-optimistic-pending class is added during the request.

📚 Examples

See usage snippets above for common patterns.

🔧 Developer Features

Console Warnings

The extension provides helpful warnings for unsupported patterns:

// ❌ These will show console warnings
"${this.querySelector('.test')}"    // DOM queries not allowed
"${window.location.href}"           // Global object access
"${JSON.parse(data)}"               // Method calls not supported

// ✅ These are supported
"${data:user-id}"                   // Data attributes
"${attr:title}"                     // HTML attributes  
"${this.value}"                     // Element properties

Lifecycle Events

Three custom events are dispatched on the optimistic target. Use event delegation to observe them:

<script>
  document.body.addEventListener('optimistic:applied', (e) => {
    const target = e.target;
    const { config } = e.detail;
    // handle start of optimistic state
  });

  document.body.addEventListener('optimistic:error', (e) => {
    const target = e.target;
    const { config, detail: errorData } = e.detail; // { status, statusText, error }
    // handle error state
  });

  document.body.addEventListener('optimistic:reverted', (e) => {
    const target = e.target;
    const { config } = e.detail;
    // handle completion of revert
  });
</script>

If you prefer htmx utilities:

<script>
  htmx.on(document.body, 'optimistic:applied', (e) => { /* ... */ });
  htmx.on(document.body, 'optimistic:error', (e) => { /* ... */ });
  htmx.on(document.body, 'optimistic:reverted', (e) => { /* ... */ });
</script>

Template References

Use <template> elements for better organization:

<div class="comments"></div>

<template id="comment-preview">
  <div class="comment"><strong>You:</strong> ${textarea}</div>
  </template>

<template id="comment-error">
  <div class="error">❌ ${error}</div>
</template>

<form hx-post="/api/comments" hx-ext="optimistic" hx-target=".comments" hx-swap="beforeend"
      data-optimistic='{"template":"#comment-preview","errorTemplate":"#comment-error","errorMode":"append"}'>
  <textarea name="comment" placeholder="Your comment here"></textarea>
  <button type="submit">Post Comment</button>
</form>

🎮 Live Demo

View the demo

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature-name
  3. Run tests: npm test
  4. Make your changes
  5. Submit a pull request

📦 Release

Tag-based releases trigger npm publish in CI:

  1. Update version in package.json if needed
  2. Create a tag and push it:
git tag v1.0.0
git push origin v1.0.0
  1. Ensure NPM_TOKEN is set in GitHub Actions secrets

📄 License

MIT License - see LICENSE for details.


hx-optimistic - Making HTMX interactions feel instant with intelligent optimistic updates. ⚡