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

@johnhenry/domkit

v0.1.1

Published

Small, independent custom elements and DOM utilities -- drop-in UI widgets, media-query-driven containers, persisted UI state, declarative loaders -- shipped as source, no build step.

Downloads

1,112

Readme

domkit

npm version CI license

Small custom elements and DOM utilities that work straight from a CDN, with no build step and no framework. Every module is independent, so you load only what you use.

<script type="module" src="https://esm.sh/@johnhenry/domkit/tabbed-ui/global.mjs"></script>

<tabbed-ui>
  <div><button>One</button><button>Two</button></div>
  <section>First panel</section>
  <section>Second panel</section>
</tabbed-ui>

Every element is held to the same contract: it behaves like a native HTML element (attributes, properties, events, forms, hidden, keyboard, and accessibility all work the way they do for built-ins), and it's tested in Chromium, Firefox, and WebKit. See docs/principles.md. Every stable element meets it.

Full documentation: opensource.johnhenry.me/domkit · Live gallery: demo/ (see Development)

Install

npm install @johnhenry/domkit
import "@johnhenry/domkit/tabbed-ui/global.mjs"; // registers <tabbed-ui>
import TabbedUI from "@johnhenry/domkit/tabbed-ui"; // or just the class
import liveQuerySelector from "@johnhenry/domkit/live-query-selector";

Or skip installing: every path above also works as https://esm.sh/@johnhenry/domkit/<path>.

Find what you need

Interface pieces

| Module | What it's for | global.mjs registers | |---|---|---| | tabbed-ui | Tabs and panels from plain children, matched by position | <tabbed-ui> | | stylable-select | A listbox whose options you can fully style | <stylable-select> | | infinite-combo-box | Autocomplete with paged ("infinite") results: filters its own options, or searches a URL or function as you type | <infinite-combo-box> | | swipe-input | Swipe gestures that send invoker commands, one per direction | <swipe-input> | | gamepad-input | Game controller buttons that send invoker commands | <gamepad-input> | | hot-key | Keyboard shortcuts (mod+k, /) that toggle a native <dialog> or popover, or run any invoker command | <hot-key> | | drill-menu | A list that drills into sub-screens and back (settings menus, mobile nav), optionally synced to the URL hash | <drill-menu> | | code-color | Syntax highlighting (JS, CSS, HTML) that never changes your markup, themable with CSS | <code-color> | | code-editor | An editable, highlighted code field that works like a <textarea> (forms, events, selection), with indent, auto-indent, and bracket pairs on the native undo stack | <code-editor> |

Responding to screen size: matchable/

| Module | What it's for | global.mjs registers | |---|---|---| | query-container | Change the element that wraps content (ul → ol, …) by media query | <query-container> | | attribute-provider | Change children's classes, styles, and attributes by media query | <attribute-provider> |

Both also have a container mode (container attribute): the same queries, evaluated against an element's size instead of the viewport.

Remembering a user's choice (e.g. a theme toggle): cyclable/

| Module | What it's for | global.mjs registers | |---|---|---| | attribute-cycler | Buttons that cycle a persisted class or attribute (theme, density), synced across tabs | <attribute-cycler> | | localstorage-attribute-cycler | The same, as a JS function | | | localstorage-cycler | The engine: a persisted value with next/previous/peek/set | |

Wiring things up from markup instead of scripts: definable/

| Module | What it's for | global.mjs registers | |---|---|---| | define-component | Register a custom element from a module URL or from inline markup | <define-component> | | polyfill-window | Load a module onto window | <polyfill-window> | | until-window-load | Hide content until the page and its <define-component>s have loaded | (strips the until-window-load class) | | definetag | Curried customElements.define | |

Timing, collections, and small helpers

| Module | What it's for | global.mjs registers | |---|---|---| | delay | await delay(ms), or await delay({ fps }) to wait one frame period on an animation frame | | | frame-timer | A clock element: steady tick events with play()/pause(), like a media element | <frame-timer> | | live-query-selector | querySelectorAll that stays current, with a change event | | | clamp | clamp(min, max)(value) | |

Bootstrapping an app: hydratable/

| Module | What it's for | |---|---| | hydratable | A run-once async hydrate() (with dehydrate() to undo it) for any object | | hydratable/mounts | Find or create the element at the start/end of <body> to render an app into |

Graphics

Plots and <chernoff-face> moved to @johnhenry/data-plot.

| Module | What it's for | global.mjs registers | |---|---|---| | draw-svg | SVG strokes that draw themselves in, CSP-safe and reduced-motion aware | <draw-svg> |

Pixel effects: moved to @johnhenry/canvas-fx

<pixel-canvas>, <pixel-sprite>, and the pixel effects (mosaic, palettes, CRT, shaders, …) are their own package now: @johnhenry/canvas-fx.

Recipes

Complete pages in examples/ that combine modules the way a real site would. Each is plain HTML you can read top to bottom:

  • Theme switcher: attribute-cycler + color-scheme, synced across tabs.
  • Command palette: hot-key + infinite-combo-box on ⌘K.
  • Settings panel: one <form> across drill-menu screens, with stylable-select, infinite-combo-box, and attribute-cycler submitting like native controls.
  • Documentation page: tabbed-ui code samples highlighted by code-color, plus query-container.
  • Game loop: frame-timer driving an animation.

How the package is laid out

  • One directory per module, under src/. Related modules are grouped one level deeper (matchable/, cyclable/, definable/, hydratable/), and the group is part of the import path.
  • @johnhenry/domkit/<module> imports the module's index.mjs: an element class (unregistered) or a function. Any file inside can be imported by its full path, e.g. @johnhenry/domkit/tabbed-ui/global.mjs.
  • global.mjs registers the element under the tag in the tables above. Import the class instead if you want a different tag name.
  • No root import. import "@johnhenry/domkit" doesn't exist, on purpose. These modules share no state or API, and a barrel would make every page download all of them, especially from a CDN where nothing tree-shakes.
  • Source is what ships. There's no build step: modern JS, ES modules only.

No dependencies

domkit has no runtime dependencies, so you can serve src/ directly, copy a module's directory into your project, or load it from any CDN, with no import map and no build step.

Content-Security-Policy

No module evaluates strings as code, so domkit works under a strict Content-Security-Policy.

Theming

Every element's optional index.css reads one shared set of design tokens (accent, highlight, border, radius, focus ring, popup surface, disabled opacity). Load theme.css for ready-made, light-dark()-aware values, or set the tokens yourself to theme every element at once:

<link rel="stylesheet" href="https://esm.sh/@johnhenry/domkit/theme.css" />
<style>
  :root { --domkit-accent: rebeccapurple; --domkit-radius: 0; }
</style>

Editor support

The package ships custom-elements.json, a standard manifest of every element's tag, attributes, properties, events, and CSS custom properties, and which module registers each tag (its global.mjs, as a custom-element-definition export). Tools that read it (Storybook, many IDE plugins, page builders) work out of the box. For VS Code's HTML autocomplete and hover docs, add the generated data file to your settings:

{ "html.customData": ["./node_modules/@johnhenry/domkit/vscode.html-custom-data.json"] }

TypeScript users get declarations for each element class and its HTMLElementTagNameMap entry, so document.querySelector("tabbed-ui") is typed, and the plain-function modules have declarations too.

The generated element reference lists every attribute, property, method, event, and CSS custom property.

Stability

Every module is stable: documented, held to the principles, tested in Chromium, Firefox, and WebKit, and versioned with semver. While the version is 0.x, a breaking change can only land in a minor release (0.1 → 0.2), never a patch, and it's always listed in CHANGELOG.md under Changed (breaking).

Development

npm test

This runs, in order: a parse check of every module (scripts/check-syntax.mjs), a reference check that every relative import, Markdown link, and documented @johnhenry/domkit/… path resolves (scripts/check-links.mjs), ESLint's no-undef rule (the one rule configured, see eslint.config.mjs), a TypeScript check of the generated declarations, and the fast behavioral tests in test/ under happy-dom.

npx playwright install   # once
npm run test:browser     # test/browser/, in Chromium, Firefox, and WebKit
npm run manifest         # regenerate custom-elements.json, types, editor data

The live gallery runs most modules at once, each in its own iframe:

npm run serve

then open http://localhost:4719/demo/. Module imports don't resolve over file://. Each module directory also has its own demo.html/demo.htm. See AGENTS.md for the full verification loop and the repo's known gotchas.

Related

  • @johnhenry/domable converts between HTML text, DOM nodes, and React-element-shaped objects, and builds custom-element classes from HTML strings. domkit used to depend on it, and is now dependency-free.

History

domkit was extracted from johnhenry/lib, a personal collection of hot-linked modules, in September 2026, and consolidated along the way: duplicates of domable were dropped, related modules were grouped into families, and a series of bugs were fixed (most of them found by actually running the modules). CHANGELOG.md has the whole story.

License

MIT