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

ivysaur

v0.2.3

Published

A simple 0 dependency reactive custom element library with JSX support.

Readme

ivysaur

reactive custom element library

🚧👷 Warning This is an in-development project, with an unstable API.

Overview

Ivysaur is a reactive custom element library that aims to provide a simple and efficient way to create custom elements with reactive data bindings. It supports JSX via it's h and Fragment functions, and uses a reactive data store to manage state and reactivity.

/*
  Import h and Fragment to use JSX
  (must set jsxFactory and jsxFragmentFactory in tsconfig.json)
*/
import { h, Fragment, Ivysaur, attribute, state, css } from "ivysaur";

class CustomElement extends Ivysaur {
  /*
    Styles can be a string or string array.
    They are scoped to the component.
  */
  static styles = css`
    button {
        background-color: blue;
        color: white;
    }
  `;

  /*
    By default custom elements are mostly isolated
    from the global document stylesheets. Enabling
    this option adds those styles to the component.
  */
  static useGlobalStyles = true;

  /*
    @attribute() makes a class getter or accessor
    reactive. If used on an accessor, setting the
    value will reflect to the DOM.
  */
  @attribute("data-foo") get foo() {
    // default value if not set on element
    return 0;
  }

  /*
    Converters can be used to transform the value from a
    string to the desired type. It's type is (string) => any.
  */
  @attribute("data-bar", { converter: Number })
  accessor bar = 0;

  /*
    @state() turns a class accessor property into a
    reactive state property. This value is deeply reactive,
    meaning objects and arrays can trigger updates when
    their properties or elements are changed.
  */
  @state() accessor count = 0;

  override onMount() {
    /*
      This callback runs when the element is mounted to the DOM,
      before the first render. It may run multiple times
      if the element is disconnected and reconnected.
    */
  }

  override onMounted() {
    /*
      This callback runs after the first render.
      It may run multiple times if the element is
      disconnected and reconnected.
    */
  }

  override onUnmount() {
    /*
      This callback runs when the element is
      removed from the DOM. It may run multiple times
      if the element is disconnected and reconnected.
    */
  }

  // define UI here
  override render() {
    /*
      Components are simple functions that are
      eagerly evaluated on each render.
    */
    let Counter = (props, children) => {
      // there are no special props. use onclick instead of onClick.
      return <button onclick={() => this.count++ * props.factor}>
        {children}
      </button>;
    }

    return (
      // All components must use <host/> as root element.
      // This can be used to bind attributes to the custom element.
      <host data-foo="bar">
        <Counter factor={2}>Increment by 2: {this.count}</Counter>
        {/*
          The attr: and prop: prefixes are used to bind values to an element's
          attributes or properties respectively. Without these prefixes, the
          element will set a property if it exists on the element, or else
          set an attribute.
        */}
        <another-element attr:data-attr="hello" prop:sayHi={() => 'world'}  />
      </div>
    )
  }
}

// Define the custom element in the global registry.
CustomElement.defineSelf("custom-element")

Light Dom

By default Ivysaur components use the shadow dom. If you want to take advantage of the lifecycle and reactivity of Ivysaur with the light dom, you can toggle the lightDom static property to true. This disables styles and JSX rendering. Instead the render function can be used to imperatively create and modify dom nodes.

class LightDomElement extends Ivysaur {

  static lightDom = true;

  @state() accessor count = 0;

  // the api supports both camelCase and snake_case
  override on_mount() {
    let button = this.querySelector("button");
    if(button) {
      button.addEventListener("click", () => this.count++);
    }
  }

  override render() {
    let button = this.querySelector("button");
    if(button) {
      button.textContent = `Increment: ${this.count}`;
    }
  }
}

Reactivity

Ivysaur exports reactive primitives that can be used outside of components.

import { reactive, effect, signal } from "ivysaur";

let s = signal(0);

effect(() => {
  console.log(s());
});

s(2) // prints "2"

// deeply reactive via proxy.
let r = reactive({
  foo: {
    bar: [1,2,3]
  }
})

effect(() => {
  console.log("the second element of r.foo.bar is", r.foo.bar[1])
})

r.foo.bar[1] = 4 // prints "the second element of r.foo.bar is 4"

License

Made with 💛

Published under MIT License.