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

web-toolkit-x

v0.72.0

Published

Web Toolkit X, a Web UI toolkit

Readme

Web Toolkit X

Web Toolkit X is a lightweight library for building Web user interfaces as a composition of small, self-contained apps. You describe what the UI should look like for a given state, and Toolkit keeps the page in sync with it.

It originates from the Opera Web UI Toolkit.

In a nutshell

  • Self-contained pieces - each part of the UI owns its state, styles and behaviour, isolated from the rest of the page, so it can be built, tested and reused on its own.
  • Just JavaScript - the UI is described with plain data and functions, with no template language to learn and no build step required.
  • Predictable state - the state changes only in explicit, named steps, and the UI always reflects the current state.
  • Built on the platform - it relies on what the browser already provides, like Web Components and ES modules, rather than working around it.
  • Mistakes caught early - types and a debug mode point out errors in the UI while it is being written.
  • Extensible - plugins add behaviour across all the apps, like logging, shared styles or helper methods, without changing them.

Usage

import toolkit, {
  WebComponent,
  type CommandsAPI,
  type Template,
} from 'web-toolkit-x'

type Props = { start: number }
type State = { count: number }

const CounterCommands = {
  increment() {
    this.count += 1
  },
} satisfies CommandsAPI<State>

class Counter extends WebComponent<State, typeof CounterCommands, Props> {
  static elementName = 'my-counter'

  static styles = ['styles/counter.css']

  static commands = CounterCommands

  getInitialState(props: Props): State {
    return { count: props.start }
  }

  render(): Template {
    return [
      'button',
      { onClick: () => this.commands.increment() },
      `Clicked ${this.props.count} times`,
    ]
  }
}

await toolkit.render(Counter, document.body, { start: 0 })

The types are optional, the same component works in plain JavaScript without them.

Toolkit is also available as a single script, exposing the toolkit global:

<script src="toolkit-0.72.0.js"></script>

The global is typed by the web-toolkit-x/global declarations, listed in types in tsconfig.json or referenced in the scripts using it:

/// <reference types="web-toolkit-x/global" />

Configuration

Toolkit renders without the debug mode and plugins by default. Both can be configured at any time, also after rendering. Options not provided keep their current values. Changed plugins are uninstalled from the created roots and the new ones installed:

toolkit.configure({ debug: true, plugins: [plugin] })

Components

Web Components render the content of an app, managing its state. Nested Web Components are rendered in their own custom elements. Their state is created from the props in getInitialState() and updated with commands, or by the parent with getUpdatedState().

Components render fragments of a Web Component from props, and pure components are just functions:

const Square = (props: { color: string; size: number }): Template => [
  'section',
  {
    class: 'square',
    style: {
      backgroundColor: props.color,
      height: [props.size, 'px'],
      width: [props.size, 'px'],
    },
  },
]

Both can define the onCreated(), onAttached(), onPropsReceived(), onUpdated(), onDestroyed() and onDetached() lifecycle methods.

Read more about Bragi templates, the Commands API and see a few examples.

TypeScript

Components are typed with their props, and their render() methods return a Template. Templates are type-checked, so misspelled props and attributes, unknown style properties or listeners of wrong event types are reported:

import { Component, type Template } from 'web-toolkit-x'

class Title extends Component<{ text: string }> {
  render(): Template {
    return ['h1', { class: 'title' }, this.props.text]
  }
}

Child templates passed to a component are available as this.children:

class Card extends Component<{ title: string }> {
  render(): Template {
    return ['section', ['h2', this.props.title], ...this.children]
  }
}

Web Components take the types of the state, the Commands API and props: WebComponent<State, Commands, Props>, with the Commands API and props optional, as in WebComponent<State>. The Commands API is typed with the state it changes, using satisfies CommandsAPI<State>, as in the usage example above.

The state can contain only primitives, functions, plain objects, arrays, maps, sets, dates and typed arrays. Commands record their changes on copies of these objects, sharing the unchanged parts with the previous state, while primitives and functions are just replaced.

Build

npm run build

It creates in dist/release:

  • index.js - an ES module with type declarations in index.d.ts,
  • toolkit-<version>.js - a single script exposing the toolkit global, typed in global.d.ts,

both with source maps, and a declaration map leading editors to the TypeScript sources.

npm run dev creates the same files in dist/dev and builds them again on every change.

Demo

npm run demo

It builds Toolkit and runs the demos with the ES module build:

  • Flemmings, the default one, with flamingos walking, wading and idling, maintained by nested Web Components,
  • Grid, at /grid.html, with tiles reordered by dragging them.

Development

Toolkit requires Node 24, as defined in .nvmrc:

nvm use
npm install
npx playwright install chromium firefox  # once, for running the tests

| Command | Description | | ----------------------- | ------------------------------------------------------------------- | | npm test | runs the tests in Chromium with Vitest | | npm run test:browsers | runs the tests in Chromium and Firefox | | npm run build | builds Toolkit into dist/release | | npm run dev | builds Toolkit into dist/dev on every change | | npm run typecheck | checks the types with TypeScript | | npm run lint | lints the code with ESLint | | npm run verify | runs the build, the type check, the linter and the formatting check | | npm run format | formats the code with Prettier | | npm run coverage | runs the tests in Chromium with the coverage report | | npm run bench | runs the benchmarks |

Git hooks format and lint the committed files, and run the checks and tests before pushing.