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 🙏

© 2026 – Pkg Stats / Ryan Hefner

lume-js

v2.2.1

Published

Minimal reactive state management using only standard JavaScript and HTML - no custom syntax, no build step required

Readme


Why Lume.js?

| Feature | Lume.js | Alpine.js | Vue | React | |---------|---------|-----------|-----|-------| | Custom Syntax | ❌ No | ✅ x-data | ✅ v-bind | ✅ JSX | | Build Step | ❌ Optional | ❌ Optional | ⚠️ Recommended | ✅ Required | | Bundle Size | ~2.23KB | ~15KB | ~35KB | ~45KB | | HTML Validation | ✅ Pass | ⚠️ Warnings | ⚠️ Warnings | ❌ JSX | | Extensible Handlers | ✅ | ❌ Built-in only | ❌ Built-in only | N/A |

Lume.js is "Modern Knockout.js" — standards-only reactivity for the modern web.


Installation

Via CDN (Recommended for simple projects)

<script type="module">
  import { state, bindDom, effect } from 'https://cdn.jsdelivr.net/npm/lume-js/dist/index.min.mjs';
</script>

Via NPM (Recommended for bundlers)

npm install lume-js
import { state, bindDom } from 'lume-js';

Browser Support

| Browser | Minimum version | |---------|-----------------| | Chrome | 49+ | | Firefox | 18+ | | Safari | 10+ | | Edge | 79+ | | IE11 | ❌ Not supported |

IE11 cannot be polyfilled — Lume uses Proxy.


Quick Start

HTML:

<div>
  <h1>Hello, <span data-bind="name"></span>!</h1>
  <input data-bind="name" placeholder="Enter your name">
</div>

JavaScript:

import { state, bindDom } from 'lume-js';

const store = state({ name: 'World' });
bindDom(document.body, store);

That's it — two-way binding, no build step, valid HTML.


Built-in Reactive Attributes

bindDom() supports these data-* attributes out of the box:

<!-- Two-way binding (inputs) / one-way (text elements) -->
<input data-bind="name">
<span data-bind="name"></span>

<!-- Boolean attributes -->
<div data-hidden="isLoading">Content</div>
<button data-disabled="isSubmitting">Submit</button>
<input data-checked="isAgreed" type="checkbox">
<input data-required="fieldRequired">

<!-- ARIA attributes -->
<button data-aria-expanded="menuOpen">Menu</button>
<div data-aria-hidden="isCollapsed">Panel</div>

Extensible Handler System

Handlers are plain objects that teach bindDom() how to interpret new data-* attributes. They live in lume-js/handlers and are entirely optional — import only the ones you use. You can also write your own with just an attr string and an apply function.

Need more reactive attributes? Import handlers or create your own — no core modification needed.

import { state, bindDom } from 'lume-js';
import { show, classToggle, stringAttr } from 'lume-js/handlers';

const store = state({
  isVisible: true,
  isActive: false,
  profileUrl: '/user/alice'
});

bindDom(document.body, store, {
  handlers: [show, classToggle('active'), stringAttr('href')]
});
<span data-show="isVisible">Visible when truthy</span>
<div data-class-active="isActive">Toggles 'active' class</div>
<a data-href="profileUrl">Profile</a>

Available Handlers (lume-js/handlers)

| Handler | HTML Example | Effect | |---------|-------------|--------| | show | data-show="key" | Shows element when truthy (el.hidden = !val) | | className | data-classname="key" | Replaces full class string (el.className = val) | | boolAttr(name) | data-readonly="key" | Toggles any boolean attribute | | ariaAttr(name) | data-aria-pressed="key" | Sets ARIA attribute to "true"/"false" | | classToggle(...names) | data-class-active="key" | Toggles individual CSS classes | | stringAttr(name) | data-href="key" | Sets string attributes (removes on null) | | htmlAttrs() | (all of the above) | One-import preset — all standard HTML + ARIA attrs |

Presets

import { formHandlers, a11yHandlers } from 'lume-js/handlers';

// formHandlers: [boolAttr('readonly')]
// a11yHandlers: [ariaAttr('pressed'), ariaAttr('selected'), ariaAttr('disabled')]

Custom Handlers

Any plain object with attr and apply works:

const tooltip = {
  attr: 'data-tooltip',
  apply(el, val) { el.title = val ?? ''; }
};

bindDom(root, store, { handlers: [tooltip] });

Addons

Addons are optional reactive pattern helpers that build on the core primitives. They handle common use cases that would otherwise require boilerplate — derived values, key observation, list rendering. Import only what you need from lume-js/addons; none are loaded by default.

import { computed, watch, repeat } from 'lume-js/addons';

| Addon | When to use | |-------|-------------| | effect(fn) (core) | Write derived values back into the store, or trigger side effects on state change | | computed(fn) | Derive a read-only value from state to consume outside the store (templates, display logic) | | watch(store, key, fn) | React to a specific key changing — DOM updates, analytics, syncing external state | | repeat(container, store, key, opts) | Render a keyed list with element reuse (no full re-render on change) | | createCleanupGroup() | Collect multiple cleanup/unsubscribe functions and dispose them all at once | | hydrateState(selector?) | Read initial state from a <script type="application/json"> tag (SSR hydration) |

Quick rule: effect for writing back into state → computed for reading outside state → watch for observing a single key → repeat for arrays in the DOM.

effect() vs computed() vs watch()

import { state, effect } from 'lume-js';
import { computed, watch } from 'lume-js/addons';

const store = state({ firstName: 'Ada', lastName: 'Lovelace', count: 0 });

// effect() — derives a value and writes it back into the store
// Use when the result lives in state and drives the DOM via data-bind
effect(() => {
  store.fullName = `${store.firstName} ${store.lastName}`;
});

// computed() — derives a value to read externally (e.g. display, logging)
// Use when the result is consumed outside the store
const doubled = computed(() => store.count * 2);
console.log(doubled.value); // 10
doubled.subscribe(val => document.title = `Count × 2: ${val}`);

// watch() — reacts to a single key changing
// Use for side effects tied to one property: analytics, localStorage, DOM sync
watch(store, 'count', (val) => {
  localStorage.setItem('count', val);
});

// watch() with { immediate: false } — skip the initial call
watch(store, 'count', (val) => {
  sendAnalytics('count_changed', val); // only on actual changes
}, { immediate: false });

Documentation

Full documentation is available in the docs/ directory:


Contributing

We welcome contributions! Please read CONTRIBUTING.md for details.

License

MIT © Sathvik C