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

ultra-light-js

v1.5.0

Published

A lightweight JavaScript framework for building reactive, state-management, and SPA applications.

Downloads

696

Readme

Ultra Light Framework

CI npm version

An ultra-lightweight and reactive mini framework for modern web development, with zero dependencies.

Features

  • Reactive State Management
  • SPA Router
  • Component System
  • Scoped CSS
  • Context API
  • Zero dependencies
  • Ultra lightweight - Less than 5KB minified
  • TypeScript - Fully typed

Installation

npm install ultra-light-js
# or
pnpm add ultra-light-js

Or via CDN, without a bundler:

<script src="https://unpkg.com/ultra-light-js"></script>
<script>
  const { ultraState, UltraComponent } = UltraLight;
</script>

Quick Start

import { ultraState, UltraComponent } from 'ultra-light-js';

function Counter(){

  //create a state
  const [getCount, setCount, subscribe] = ultraState(0);

  //this function will execute when the state changes, and by default
  //it receives a reference to the trigger owner as the first argument.
  //this allows for granular DOM manipulation
  function onIncrement(
    $h1
  ){
    $h1.innerHTML = `Counter:${getCount()}`;
  }

  return UltraComponent({
    
    component: '<div></div>', //this is the root node. It accepts children in plain HTML
    
    children: [
      
      UltraComponent({
        component: `<h1>Counter:${getCount()}</h1>`,
        trigger: [ //this is where the magic happens! when the subscriber state changes, the trigger function is executed
          { subscriber: subscribe, triggerFunction: onIncrement}
        ]
      }),

      UltraComponent({
        component: '<button>Increment</button>',
        eventHandler: { click: () => setCount(getCount() + 1) }
      }) 

    ]

  });

}

Development

This is a work in progress. The API will change frequently. Contributions are welcome! Please open an issue or PR.

API

ultraState(initialValue)

Creates a reactive state.

const [getValue, setValue, subscribe] = ultraState(0);

// Get value
console.log(getValue()); // 0

// Set value
setValue(5);

// Subscribe to changes
const unsubscribe = subscribe((newValue) => {
  console.log('New value:', newValue);
});

// Unsubscribe
unsubscribe();

ultraScope(fn)

Runs fn inside an implicit owner scope: any ultraState/ultraCompState subscription made synchronously during fn's execution is auto-registered for disposal, so you don't have to manually collect and thread unsubscribe functions through a cleanup array. UltraRouter uses this internally to dispose route-scoped subscriptions on navigation, but it's also exported for apps that mount components outside the router and want the same guarantee.

const [items, setItems, subscribeItems] = ultraState([]);

const [result, disposeScope] = ultraScope(() => {
  // any subscription made synchronously here is auto-registered
  subscribeItems((value) => console.log('items changed:', value));
  return 'some result';
});

// later, tear down every subscription made inside the scope at once
disposeScope();

Only subscriptions made synchronously inside fn are captured — anything subscribed after an await, inside an event handler, or in a setTimeout runs with no active scope and still needs explicit cleanup/trigger wiring.

ultraCompState(initialComp)

Creates a composite stateful object. Each key becomes a reactive state with get, set, and subscribe. Functions receive the composite state object as the first argument, allowing methods to access other state values.

const store = ultraCompState({
  count: 0,
  name: 'John',
  // functions become methods on the store
  increment: (state) => state.count.set(state.count.get() + 1),
  reset: (state) => {
    state.count.set(0);
    state.name.set('John');
  }
});

// Access state
store.count.get();       // 0
store.count.set(5);
store.count.subscribe((v) => console.log(v));

// Call methods
store.increment();
store.reset();

UltraContext(initialValue, displayName?)

Creates a scoped context that can be owned by a specific DOM node. All methods require the calling node as the first argument. If the node is not a descendant of the owner, the operation is blocked.

const ThemeContext = UltraContext('light', 'ThemeContext');

// Assign an owner node (can only be set once)
ThemeContext.own(myRootNode);

// Set value (requires caller node to be inside owner)
ThemeContext.set(myNode, 'dark');

// Get value
console.log(ThemeContext.get(myNode)); // 'dark'

// Subscribe
const unsub = ThemeContext.subscribe(myNode, (theme) => {
  console.log('Theme changed:', theme);
});
unsub(); // unsubscribe

ultraStyles(cssString)

Creates automatically scoped styles. Returns a map of original class names to hashed class names.

const styles = ultraStyles(`
  .container {
    padding: 20px;
    background: white;
  }
  .title {
    color: blue;
    font-size: 24px;
  }
`);

// Use scoped classes
const Component = `<div class="${styles.container}">
  <h1 class="${styles.title}">Title</h1>
</div>`;

ultraStyles2(cssObject, document?)

Like ultraStyles, but takes a CSS-in-JS object (camelCase properties) instead of a CSS string. Returns a map of original keys to hashed class names.

const styles = ultraStyles2({
  container: { display: 'flex' },
  title: { fontSize: '1rem', color: 'blue' }
});

const Component = `<div class="${styles.container}">
  <h1 class="${styles.title}">Title</h1>
</div>`;

ultraQueryParams()

Gets URL search parameters as a plain object.

// URL: ?name=John&age=30
const params = ultraQueryParams();
console.log(params); // { name: 'John', age: '30' }

ultraQuery()

Creates a fetcher with built-in caching, request de-duplication, and stale-time invalidation.

const { fetch, isFetching, hasError, cache, invalidateCache, subscribeToCache } = ultraQuery();

const result = await fetch('user-1', () => window.fetch(`/api/users/1`).then(r => r.json()), 60 * 1000);
// result.data, result.isFetching(), result.hasError()

// Concurrent calls with the same key are de-duplicated, and results are cached
// until manually invalidated or the staleTime (ms, default 5 minutes) elapses.
invalidateCache('user-1');

subscribeToCache(() => console.log('cache changed:', cache()));

UltraRouter(...routes)

Creates a router for SPA navigation.

import { UltraRouter, UltraLink } from 'ultra-light-js';

const Home = () => '<div><h1>Home</h1></div>';
const About = () => '<div><h1>About</h1></div>';
const User = (params) => `<div><h1>User: ${params.id}</h1></div>`;

const router = UltraRouter(
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/user/:id', component: User },
  { path: '/*', component: () => '<h1>404 Not Found</h1>' }
);

document.body.appendChild(router);

Each route's component function is run inside its own ultraScope, so any ultraState/ultraCompState subscription made synchronously inside it is automatically disposed on navigation or when the router is cleaned up — no need to manually collect and pass those subscriptions into a cleanup array.

UltraLink(props)

Creates SPA navigation links. Ctrl/Meta+click opens in a new tab normally. Shares the same props as UltraComponent (eventHandler, attributes, styles, className, children, trigger, onMount, cleanup), applied to the underlying anchor element, plus href and viewTransition.

const link = UltraLink({
  href: '/about',
  children: ['<span>Go to About</span>'],

  // viewTransition uses the View Transition API when available
  viewTransition: true,

  // Same shared props as UltraComponent
  eventHandler: {
    mouseenter: () => console.log('hovered')
  },
  className: ['nav-link']
});

ultraNavigate({ href, viewTransition? })

Navigates programmatically within a UltraRouter context (pushes history state, scrolls to top, dispatches popstate). Use this when you need to navigate outside of a click handler, e.g. UltraLink uses it internally.

ultraNavigate({ href: '/dashboard' });

// Use the View Transition API when available, falling back to a direct
// navigation otherwise.
ultraNavigate({ href: '/dashboard', viewTransition: true });

UltraComponent(props)

Creates a component with event handlers, styles, class names, children, reactive triggers, lifecycle hooks, and cleanup.

const Button = UltraComponent({
  component: '<button>Click me</button>',

  // Event handlers - object with event names as keys
  eventHandler: {
    click: () => alert('Clicked!'),
    mouseenter: () => console.log('hovered')
  },

  // Inline styles
  styles: {
    backgroundColor: 'blue',
    color: 'white'
  },

  // CSS class names to add
  className: ['btn', 'btn-primary'],

  // Child elements (strings, HTMLElements, or null for conditional rendering)
  children: ['<span>Icon</span>', null],

  // Reactive triggers - run when state changes
  trigger: [{
    subscriber: subscribe,
    triggerFunction: (node) => {
      node.querySelector('#count').textContent = getCount();
    },
    defer: false // set true to defer to next animation frame
  }],

  // A triggerFunction may optionally return a cleanup function, the same
  // way onMount does. It runs before the *next* firing (tearing down
  // whatever the previous firing set up) and, if one is still pending,
  // on component teardown as well:
  //
  // trigger: [{
  //   subscriber: subscribe,
  //   triggerFunction: (node) => {
  //     const id = setInterval(() => node.classList.toggle('blink'), 500);
  //     return () => clearInterval(id);
  //   }
  // }],

  // Called immediately after mount (next animation frame)
  onMount: [(node) => node.focus()],

  // Cleanup functions called on component teardown
  cleanup: [() => clearInterval(myInterval)]
});

UltraActivity(props)

Shows or hides an element based on state. Shares the same props as UltraComponent, plus mode and type.

const [isVisible, setVisible, subscribeVisible] = ultraState(true);

const ConditionalDiv = UltraActivity({
  component: '<div>Only visible when isVisible is true</div>',

  // mode controls visibility
  mode: {
    state: isVisible,               // getter function returning boolean
    subscriber: subscribeVisible    // or an array of subscribers
  },

  // 'display' (default) toggles display:none | ''
  // 'visibility' toggles visibility:hidden | visible
  type: 'display'
});

UltraFragment(...children)

Groups multiple elements into a DocumentFragment without a wrapper node. Accepts null values for conditional rendering.

const fragment = UltraFragment(
  '<div>Element 1</div>',
  someCondition ? '<div>Element 2</div>' : null,
  '<div>Element 3</div>'
);

ultraReplaceChildren(parent, ...newChildren)

Replaces parent's children the way native Element.replaceChildren() does, except any outgoing child that carries _cleanup (built with UltraComponent/UltraActivity/UltraLink, or manually assigned) has it invoked first, so event listeners, trigger subscriptions, and onMount-returned cleanups don't leak. Plain children without _cleanup are skipped, not touched.

Prefer this over calling parent.replaceChildren(...) directly whenever parent may contain UltraLightElement children — the native DOM API gives nothing else in the library a hook into node removal, so calling .replaceChildren() yourself on a parent holding library-built children means those children's cleanup is silently skipped unless you call ._cleanup() on each outgoing node yourself first (which in turn means keeping a live reference to them at swap time, since replaceChildren doesn't hand back what it removed).

const list = document.querySelector('#list');

// Safe: outgoing items' _cleanup (listeners, triggers, onMount cleanups) runs first
ultraReplaceChildren(list, ...newItems.map(item => UltraComponent({
  component: `<li>${item.label}</li>`,
  eventHandler: { click: () => selectItem(item.id) }
})));

UltraErrorBoundary(props)

Wraps one or more zero-arg component factories in a try/catch, rendering fallback instead if any factory throws. A single factory's result is returned directly; multiple factories are combined with UltraFragment if they all succeed. Synchronous only — a rejected promise from an async factory is not caught.

const Safe = UltraErrorBoundary({
  factories: () => UltraComponent({ component: riskyMarkup() }),
  fallback: (error) => UltraComponent({ component: `<div>Something went wrong: ${error}</div>` })
});

ultraPortal(app, portal)

Inserts a component directly after a given application root element, outside of the normal component tree. Useful for modals, tooltips, or anything that needs to escape a parent's overflow/z-index stacking context. Throws if the app element or the portal content can't be resolved.

// app: a CSS selector or an HTMLElement identifying the mount point
ultraPortal('#app', '<div class="modal">Hello from a portal</div>');

Examples

Complete Todo App

import { ultraState, UltraComponent, ultraStyles } from 'ultra-light-js';

const styles = ultraStyles(`

  .todo-app {
    max-width: 600px;
    margin: 0 auto;
  }

  .todo-item {
    padding: 10px;
    border: 1px solid #ddd;
    margin: 5px 0;
  }

`);

function TodoApp(){

  const [getTodos, setTodos, subscribeTodos] = ultraState([]);
  const [getInput, setInput, subscribeInput] = ultraState('');

  function onListChange($list){
    $list.innerHTML = getTodos()
      .map(todo => `<div class="${styles['todo-item']}">${todo.text}</div>`)
      .join('');
  }

  function onInputChange($input){
    $input.value = getInput();
  }

  return UltraComponent({
    
    component: '<div></div>',

    className: [styles['todo-app']],
    
    children: [

      UltraComponent({
        component: '<input type="text" placeholder="New task..." />',
        eventHandler: { input: (e) => setInput(e.target.value) },
        trigger: [{ subscriber: subscribeInput, triggerFunction: onInputChange }]
      }),

      UltraComponent({
        component: '<button>Add</button>',
        eventHandler: {
          click: () => {
            if (getInput().trim()) {
              setTodos([...getTodos(), { id: Date.now(), text: getInput() }]);
              setInput('');
            }
          }
        }
      }),

      UltraComponent({
        component: '<div></div>',
        trigger: [{ subscriber: subscribeTodos, triggerFunction: onListChange }]
      })

    ]
  });

}

document.body.appendChild(
  TodoApp()
);

License

GPL-3.0

Author

Amin Perez Alconchel