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

@m41na/reactive-html

v1.0.1

Published

Zero-dependency reactive framework that brings Vue-like reactivity directly to HTML

Readme

ReactiveHTML 🚀

A lightweight, zero-dependency reactive framework that brings Vue-like reactivity directly to HTML.

No build step. No virtual DOM. No JSX. Just HTML with superpowers.

Why ReactiveHTML?

<!-- This just works. No compilation needed. -->
<div data-model="app">
  <input :model="username">
  <p>Hello, <span :text="username"></span>!</p>
</div>

<script src="reactive-html.js"></script>
<script>
  const model = ReactiveHTML.reactive({
    app: { username: 'World' }
  });

  const parser = new ReactiveHTML.ReactiveHTMLParser();
  const root = document.querySelector('[data-model]');
  const parsed = parser.parse(root);
  ReactiveHTML.createBindings(parsed, model);
</script>

That's it. Type in the input, the text updates. Change the model, the input updates. Pure reactivity, zero magic.

Features

✅ Reactive Data - Deep reactivity for objects and arrays

✅ Two-Way Binding - :model for forms (text, checkbox, radio, select, textarea)

✅ Loops - :each with keyed reconciliation for efficient updates

✅ Conditionals - :if/:else-if/:else with lazy evaluation

✅ Events - @click, @input, etc. with proper this binding

✅ Computed Properties - Cached, reactive derived state

✅ Batch Updates - Automatic batching with requestAnimationFrame

✅ Zero Dependencies - Pure JavaScript, no build step

✅ Tiny Size - ~15KB minified (~5KB gzipped)

Installation

CDN

<script src="https://unpkg.com/reactive-html@latest/dist/reactive-html.js"></script>

NPM

npm install reactive-html
import { reactive, ReactiveHTMLParser, createBindings } from 'reactive-html';

Quick Start

  1. Reactive Data Binding
<div data-model="app">
  <h1 :text="title"></h1>
  <p :text="description"></p>
</div>

<script>
  const model = ReactiveHTML.reactive({
    app: {
      title: 'Hello World',
      description: 'Reactivity made simple'
    }
  });

  // Parse and bind
  const parser = new ReactiveHTML.ReactiveHTMLParser();
  const root = document.querySelector('[data-model]');
  const parsed = parser.parse(root);
  ReactiveHTML.createBindings(parsed, model);

  // Changes automatically update the DOM
  setTimeout(() => {
    model.app.title = 'Hello ReactiveHTML!';
  }, 2000);
</script>
  1. Two-Way Form Binding
<form data-model="form">
   <input type="text" :model="username" placeholder="Username">
   <input type="email" :model="email" placeholder="Email">

  <label>
    <input type="checkbox" :model="subscribe">
    Subscribe to newsletter
  </label>

<button @click="submit()">Submit</button>

  <pre :text="JSON.stringify(this, null, 2)"></pre>
</form>

<script>
  const model = ReactiveHTML.reactive({
    form: {
      username: '',
      email: '',
      subscribe: false,

      submit() {
        console.log('Form data:', {
          username: this.username,
          email: this.email,
          subscribe: this.subscribe
        });
      }
    }
  });
</script>
  1. Lists with Loops
<div data-model="app">
  <button @click="addItem()">Add Item</button>

 <ul>
   <li :each="item in items" :key="item.id">
     <span :text="item.name"></span>
     <button @click="removeItem(item.id)">Remove</button>
   </li>
 </ul>
</div>

<script>
 const model = ReactiveHTML.reactive({
   app: {
     items: [
       { id: 1, name: 'Coffee' },
       { id: 2, name: 'Tea' }
     ],

     addItem() {
       this.items.push({
         id: Date.now(),
         name: `Item ${this.items.length + 1}`
       });
     },

     removeItem(id) {
       const index = this.items.findIndex(i => i.id === id);
       this.items.splice(index, 1);
     }
   }
 });
</script>
  1. Conditionals
<div data-model="app">
   <button @click="toggle()">Toggle</button>

  <p :if="count === 0">No items</p>
  <p :else-if="count < 5">A few items</p>
  <p :else>Many items!</p>
</div>

<script>
  const model = ReactiveHTML.reactive({
    app: {
      count: 0,
      toggle() {
        this.count = (this.count + 1) % 10;
      }
    }
  });
</script>
  1. Computed Properties
<div data-model="cart">
 <div :each="item in items" :key="item.id">
   <span :text="item.name"></span> - $<span :text="item.price"></span>
 </div>

 <div>
   <strong>Total:</strong> $<span :text="total.toFixed(2)"></span>
 </div>
</div>

<script>
 const model = ReactiveHTML.reactive({
   cart: {
     items: [
       { id: 1, name: 'Coffee', price: 4.50 },
       { id: 2, name: 'Tea', price: 3.00 }
     ],

     // Computed property using getter
     get total() {
       return this.items.reduce((sum, item) => sum + item.price, 0);
     }
   }
 });
</script>

API Reference

Directives

Property Binding

  • :text - Set textContent
  • :html - Set innerHTML (use carefully)
  • :value - Set value attribute
  • :class - Set class (object or string)
  • :style - Set style (object or string)
  • :disabled, :checked, etc. - Set boolean attributes
  • :model - Two-way binding for form inputs

Event Binding

  • @click, @input, @change, etc. - Attach event listeners
  • Access event with $event, element with $element, target with $target

Control Flow

  • :each="item in items" - Loop over arrays
  • :key="item.id" - Key for efficient updates
  • :if="condition" - Conditional rendering
  • :else-if="condition" - Else-if branch
  • :else - Else branch

Component Binding

  • data-model="modelName" - Bind element to model property

Core API

reactive(obj)

Makes an object reactive with deep reactivity.

const state = reactive({
count: 0,
user: { name: 'John' }
});

state.count++; // Triggers updates
state.user.name = 'Jane'; // Also triggers updates

computed(getter, context)

Creates a computed property that caches its value.

const doubled = computed(() => state.count * 2);
console.log(doubled.value); // 0
state.count++;
console.log(doubled.value); // 2

batch(fn)

Executes multiple updates in a single batch.

batch(() => {
state.count++;
state.user.name = 'Jane';
state.items.push(...newItems);
});
// Only one DOM update!

nextTick(callback)

Waits for the next DOM update cycle.

state.count++;
await nextTick();
console.log('DOM updated!');

Comparison

| Feature | ReactiveHTML | Vue | React | Svelte | |-----------------|--------------|------------|------------|------------| | Build Step | ❌ None | ✅ Required | ✅ Required | ✅ Required | | Virtual DOM | ❌ | ✅ | ✅ | ❌ | | Bundle Size | 5KB | 34KB | 44KB | varies | | Two-Way Binding | ✅ | ✅ | ❌ | ✅ | | Computed | ✅ | ✅ | ❌ | ✅ | | Learning Curve | Minimal | Medium | Steep | Medium |

Philosophy

  • HTML First. Your HTML is your template. No JSX, no template strings, no separate files.
  • Zero Magic. Everything is explicit. If you can read HTML and JavaScript, you can read ReactiveHTML.
  • Progressive Enhancement. Start with static HTML, add reactivity where you need it.
  • Framework Weight Zero. No build tools, no CLI, no ecosystem. Just drop in the script and go.

Browser Support

  • Chrome/Edge 90+
  • Firefox 88+
  • Safari 14+

(Basically any browser with Proxy support)

Contributing

Contributions welcome! This is a learning project that became surprisingly useful. License MIT © 2025

Made with ❤️ and a lot of coffee. "Sometimes the best framework is the one you build yourself."