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

@zoxon/nux

v1.1.0

Published

Nano framework for building UI components in TypeScript.

Readme

Nux Logo

Nux — tiny core. total control.

📷 TL;DR

Nux is a zero-dependency, TypeScript-first micro-framework for DOM components.

  • ✅ Build isolated components with lifecycle and refs.
  • ♻️ Destroy components with a single line.
  • 💡 Use in Astro, static HTML, or any SSR stack.

📛 Why "Nux"?

The name Nux combines several meaningful layers that reflect the core qualities of the framework:

  • Nux as new UX — emphasizing the creation of a modern, flexible, and enhanced user experience layer for web applications.
  • Nux as nucleus — symbolizes the core, the origin from which everything else is born. Minimalistic and foundational architecture that kickstarts UI development.
  • Nux as nut — small, compact, yet incredibly powerful. A framework with enormous potential packed into a tiny size.

Thus, Nux is a lightweight, basic, yet powerful and extensible tool that energizes and structures your frontend components.

🌟 What is Nux?

  • ⚡️ Zero Dependencies – Lightweight core, no external runtime packages.
  • 🧠 Fully Typed API – Built entirely with TypeScript. Intellisense included.
  • 🧩 Component-Based Architecture – Isolated logic with lifecycle, actions, and state.
  • 🎯 Direct DOM Control – Fine-grained event binding with scoped data-refs and native API.
  • 🔥 Scoped Event System – Nux leverages the native browser EventTarget API (no polyfills, full performance).
  • ♻️ Clean Destroy Logic – One-liner teardown of listeners, timeouts, or observers.
  • 🛠 Framework-Agnostic – Works with Astro, SSR, or plain HTML.
  • 🚀 Zero Runtime Overhead – No hydration or reactivity overhead unless you need it.
  • 📦 Tiny Footprint – Under 1KB when minified and gzipped.

🚀 Getting Started

  1. Install

    npm install @zoxon/nux
  2. Import the library in your JavaScript or TypeScript file:

    import { Component, defineComponent } from '@zoxon/nux'
  3. Create a component by extending the Component class:

     import { Component } from '@zoxon/nux'
    
     class Counter extends Component {
       count = 0
    
       init() {
         this.get('increment')?.addEventListener('click', () => {
           this.count++
           this.get('output')!.textContent = String(this.count)
         })
       }
     }
  4. HTML Markup

    <div data-component="counter">
      <button data-ref="increment">Increment</button>
      <span data-ref="output">0</span>
    </div>
  5. Register and Initialize

    import { defineComponent, initComponents } from '@zoxon/nux'
    
    defineComponent('counter', Counter)
    initComponents({ scope: 'page' })

🛠 API Reference

Every component extends the Component base class, giving you lifecycle methods and utilities to interact with the DOM and other components in a clean, consistent way.

Lifecycle Methods

  • init(): Called automatically when the component is initialized.
  • buildCache(): Called during construction to process refs and perform pre-binding setup.
  • bindEvents(): Bind all DOM and custom events here. Called after buildCache().
  • destroy(): Optional cleanup method called when the component is destroyed.
export class MyComponent extends Component {
  async init() {
    console.log('Component is live!')
  }

  buildCache() {
    this.title = this.get('title')
  }

  bindEvents() {
    this.title?.addEventListener('click', () => alert('Clicked!'))
  }

  destroy() {
    console.log('Component removed')
  }
}

Utility Methods

get(name: string): HTMLElement | undefined - Returns a single element by data-ref. Should be prefixed with component name in markup. If multiple components may be on the page, prefix data-ref with the component name (e.g., data-ref="modal:title"). Otherwise, plain data-ref="title" is fine.

<div data-ref="modal:title">Hello</div>
const title = this.get('title')

getAll(name: string): HTMLElement[] - Returns an array of elements by data-ref. Should be prefixed with component name in markup.

<div data-ref="modal:button">Button 1</div>
<div data-ref="modal:button">Button 2</div>
const buttons = this.getAll('button')

Component Properties

| Property | Type | Description | | --------- | --------------------- | ---------------------------------------- | | name | string | The name of the component. | | element | HTMLElement | The root DOM element. | | options | Record<string, any> | Optional config passed to the component. | | data | Record<string, any> | Custom data passed to the component. | | rawRefs | Reference[] | Raw references parsed from DOM. |

🔗 Cross-Component Events

Your components can talk to each other using any event system like @zoxon/eventor — a lightweight, type-safe event system built on top of native custom events.

  1. Install:

    npm install @zoxon/eventor
  2. Update the events map type:

      declare interface WindowEventMap {
        'modal:show': CustomEvent<{ id: string }>
        'modal:close': CustomEvent<{ id: string }>
      }
  3. Listen an event:

     import { Component } from '@zoxon/nux'
     import { listenEvent } from '@zoxon/eventor'
    
     export class Modal extends Component {
       close() {
         listenEvent('modal:close', (event) => {
           const { id } = event.detail
           // Handle the event, e.g., close the modal with the given id
         })
    
         listenEvent('modal:show', (event) => {
           const { id } = event.detail
           // Handle the event, e.g., show the modal with the given id
         })
       }
     }
  4. Dispatch an event:

     import { Component } from '@zoxon/nux'
     import { dispatchEvent } from '@zoxon/eventor'
    
     export class SomeComponent extends Component {
       showModal(id: string) {
        const button = this.get<HTMLButtonElement>('button')
         if (button) {
           button.addEventListener('click', () => {
             dispatchEvent('modal:show', { id })
           })
         }
       }
     }

🧪 Complex Component Example

<div data-component="counter">
  <div data-ref="counter:result"></div>
  <button type="button" data-ref="counter:plus">+</button>
  <button type="button" data-ref="counter:minus">-</button>
</div>
  import { Component, defineComponent } from '@zoxon/nux'

  // Extend Counter class
  interface Counter {
    plusButton?: HTMLButtonElement
    minusButton?: HTMLButtonElement
    result?: HTMLElement
    counter: number
  }

  class Counter extends Component {
    // Init class variables
    buildCache() {
      this.plusButton = this.get<HTMLButtonElement>('plus')
      this.minusButton = this.get<HTMLButtonElement>('minus')
      this.result = this.get<HTMLElement>('result')
      this.counter = 0
    }

    bindEvents() {
      if (this.plusButton) {
        this.plusButton.addEventListener('click', () => {
          this.counter++
          this.renderResult()
        })
      }

      if (this.minusButton) {
        this.minusButton.addEventListener('click', () => {
          this.counter--
          this.renderResult()
        })
      }
    }

    // Render result once
    init() {
      this.renderResult()
    }

    renderResult() {
      if (this.result) {
        this.result.innerHTML = this.counter.toString()
      }
    }
  }

  // Init component on element with data-component="counter" attribute
  defineComponent('counter', Counter)

🤝 Contributing

We welcome PRs, issues, and ideas! Open a discussion or submit a patch — let’s build a better component model together.

🪪 License

MIT © Konstantin Velichko