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

@joecritch/behave.js

v0.1.0

Published

Gradually layer a website's UI with JavaScript behavior, and keep it easy to understand and predict

Readme

behave.js

Gradually layer a website's UI with JavaScript behavior, and keep it easy to understand and predict.

Useful for websites that need a bit of JS behavior. Less useful for applications where the UI changes intricately over time, where more functional components may be required (see React for that).

Contents

Installation

  • npm install @joecritch/behave.js --save

How-To

Knowledge required for this how-to:

  • HTML & JavaScript
  • ES modules
  • ES6 arrow functions

Initialize a behavior

Firstly, we need to connect the HTML to JavaScript, in order to initialize the correct behavior.

<!-- index.html -->

<!-- + the usual DOCTYPE, etc. -->
<button data-behavior="Toggle">
  Toggle me
</button>
// src/Toggle.js

import { createBehavior } from 'behave.js';

const Toggle = createBehavior('Toggle', {
  init() {
    console.log('Hello?');
  },
});

export default Toggle;

(If you prefer, you can write behaviors as ES6 classes.)

// src/index.js

import { manageBehaviors } from 'behave.js';
import Toggle from './Toggle'; // (The behavior you created before)

document.addEventListener('DOMContentLoaded', () => {
  manageBehaviors({
    Toggle,
  });
});

Render

Each behavior supports a render property. This object can "describe", and therefore affect, the values of DOM attributes (in this case, the style attribute).

const Toggle = createBehavior('Toggle', {
  render: {
    attributes: {
      style: {
        color: () => 'red',
      },
    },
  };
});

^ Here, the button's text turns red immediately.

Each of the object's functions (e.g. color) is called whenever the behavior is updating. The values they return will be used with the respective DOM API operation. In this case: node.style.color = "red";.

Of course, the example above doesn't do anything that vanilla JS couldn't. (Or bog-standard HTML for that matter!) So let's dig into some features that might convince you...

Change state

Behaviors can store their own state, which can be changed by various means.

const Toggle = createBehavior('Toggle', {
  getInitialState() {
    return {
      isOn: false,
    };
  },

  init() {
    setTimeout(() => {
      this.setState({
        isOn: true,
      });
    }, 1000);
  },

  render: {
    attributes: {
      style: {
        color: _ => _.state.isOn ? 'red' : null,
      },
    },
  };
});

^ The isOn state changes to true after 1 second. Therefore, the button's text turns red after 1 second.

Now, as promised, the color function serves a purpose! Rather than always returning "red", it now depends on the state of the component. If isOn is true, it'll be red; else, the color attribute will be removed (when it returns null).

(Note: the _ argument of the function. This is the behavior's instance that is currently being updated. Of course, this can be called something else of course, if required. Also, if you want to avoid arrow functions and rely on this context instead, you can: function() { console.log(this) }, where this is the behavior instance.)

Listen to events

Behaviors can also listen to native DOM events, using a similar declarative syntax.

const Toggle = createBehavior('Toggle', {
  getInitialState() {
    return {
      isOn: false,
    };
  },

  handleClick() {
    this.setState({
      isOn: !this.state.isOn,
    });
  },

  render: {
    attributes: {
      style: {
        color: _ => _.state.isOn ? 'red' : null,
      },
    },
    listeners: {
      click: _ => _.handleClick,
    },
  },
});

^ Here, when the button is clicked, its text turns red.

This is a convenient way to manage event listeners. Bonus: if the click function above returned null, it would remove any previous listener. (So, whether a listener is active could also be based on _.state.)

Define children

You can describe child nodes for a behavior, using a data-BehaviorName-childname syntax:

.hide { display: none; }
<button data-behavior="Toggle">
  Turn me
  <span data-Toggle-ontext>on</span>
  <span data-Toggle-offtext class="hide">off</span>
</button>
// ... rest of Toggle.js
  // render: {
    children: {
      'ontext': {
        attributes: {
          style: {
            display: _ => _.state.isOn ? 'none' : null,
          },
        },
      },
      'offtext': {
        attributes: {
          style: {
            display: _ => _.state.isOn ? null : 'none',
          },
        },
      },
    },
  // },
// ... rest of Toggle.js

This also works if you have multiple children of the same name.

Connect a child to another behavior

A child can also reference another behavior via the markup:

<div data-behavior="Panel">
  <button data-Panel-btn="Toggle">
    <span data-Toggle-ontext>on</span>
    <span data-Toggle-offtext class="hide">off</span>
  </button>
</div>

We no longer reference Toggle from data-behavior, but instead define it as a child of the new Panel behavior.

Here is the new Panel behavior. (No changes would be required to Toggle at this point.)

const Panel = createBehavior('Panel', {
  render: {
    children: {
      btn: {},
    },
  },
});

Send data to a child

Behaviors can send data to their child behaviors using props.

Imagine the situation where Panel also needed to change its appearance when you click the toggle button. To do this, we'll move the isOn state to the Panel behavior instead. Then, we'll send isOn as a prop down to Toggle.

Furthermore, props support most common data types, including functions. By passing down functions, this allows the child to call it, like a callback. We'll use this pattern below, for the click event.

const Panel = createBehavior('Panel', {
  getInitialState() {
    return {
      isOn: false,
    };
  },

  handleBtnClick() {
    this.setState({
      isOn: !this.state.isOn,
    });
  },

  render: {
    children: {
      btn: {
        isOn: _ => _.state.isOn,
        onClick: _ => _.handleBtnClick,
      },
    },
  },
});
.panel { background-color: #ccc; padding: 10px; }
.panel.is-on { background-color: yellow; }
const Toggle = createBehavior('Toggle', {
  handleClick() {
    this.props.onClick();
  },

  render: {
    attributes: {
      style: {
        color: _ => _.props.isOn ? 'red' : null,
      },
    },
    listeners: {
      click: _ => _.handleClick,
    },
    children: {
      'ontext': {
        attributes: {
          style: {
            display: _ => _.state.isOn ? 'none' : null,
          },
        },
      },
      'offtext': {
        attributes: {
          style: {
            display: _ => _.state.isOn ? null : 'none',
          },
        },
      },
    },
  },
});

ES6 Classes

There is also an ES6 class alternative for defining behaviors.

You access it like so:

import { Behavior } from 'behave.js';

export default class Toggle extends Behavior {
  constructor(...args) {
    super(...args);
    // Unlike when using `createBehavior()`, custom methods aren't auto-bound
    this.handleClick = this.handleClick.bind(this);

    this.state = {
      // (Or use getInitialState)
    };

    this.render = {
      // ... You render object
    };
  }

  // Your custom methods
  handleClick() {

  }
}

export default Toggle;

Class properties

Optionally, if you have babel-preset-stage-2 or similar installed, you can use class properties too, for a terser syntax:

import { Behavior } from 'behave.js';

export default class Toggle extends Behavior {
  state = {
    // (Or use getInitialState)
  };

  // (`this` is properly bound, as its an arrow function)
  handleClick = () => {

  };

  render = {
    // ... You render object
  };
}

export default Toggle;

Another demo

There is another demo which is slightly more "real world". It's located in the demo folder of this repo. Here's how to access it:

  1. Clone this repo
  2. cd into the project root
  3. $ yarn && yarn build to compile behave.js
  4. $ cd demo && yarn && yarn demo
  5. & open http://localhost:8081

FAQ

How FAST is it?

TBC!

Why "behaviors"?

This project doesn't use the term "component", because it doesn't take full responsibility for its DOM structure.

More suitably, the term "behavior" comes from AREA 17's concept of referencing a function from a DOM attribute, which in itself originates from elementaljs. You should check both those out too.

How does it compare to React?

Unlike React or other functional UI libraries, the render property not a function. Instead, its an object, because its structure is not designed to change.

This also greatly reduces complexity in the internal "diff". However, the values within the object are functions, because they are designed to change, based on its input.

Why are these FAQs so bad?

Please contribute via issues or PRs! I'd like to make this as useful as possible.

TODO

  • [ ] Write tests
  • [ ] Add Flow annotations
  • [ ] Test overall performance