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

@0xtlt/devlens

v0.2.7

Published

Lightweight frontend devtools panel for any web app

Readme

DevLens

A lightweight, zero-dependency devtools panel you can drop into any web app. It gives you a floating tab bar with live plugins for network inspection, repaint tracking, console mirroring, accessibility auditing, SEO checks and heading outlines — without touching browser devtools.

Built in vanilla TypeScript. No runtime dependencies. ~20 KB gzipped for the full bundle.

Install

npm install @0xtlt/devlens
# or
bun add @0xtlt/devlens

Quick start

import { devlens, allPlugins } from '@0xtlt/devlens'

devlens({ plugins: allPlugins() })

Or pick only the plugins you need:

import { devlens, consolePlugin, seoPlugin } from '@0xtlt/devlens'

devlens({ plugins: [consolePlugin(), seoPlugin()] })

No bundler? Drop it into any HTML page via esm.sh:

<script type="module">
  import { devlens, allPlugins } from 'https://esm.sh/@0xtlt/devlens'
  devlens({ plugins: allPlugins() })
</script>

The panel mounts in the bottom-right corner. Press Ctrl+Shift+D or click the 🔍 button to open it.

Only load DevLens in development — it mutates the DOM and patches a few globals (console.error, console.warn, addEventListener). A common pattern:

if (import.meta.env.DEV) {
  const { devlens, consolePlugin /* … */ } = await import('@0xtlt/devlens')
  devlens({ plugins: [consolePlugin()] })
}

Plugins

Each plugin is a factory — call it to get a plugin instance, pass the result to devlens({ plugins: [...] }).

| Factory | Tab | What it does | | --- | --- | --- | | networkPlugin() | 🌐 Network | Reads performance.getEntriesByType('resource') to list requests, group by domain, and flag slow / oversized transfers. Read-only — does not patch fetch. | | repaintsPlugin() | 🎨 Repaints | Watches MutationObserver events and draws a canvas overlay that highlights frequently-mutating elements. Color ramps from teal (x1–2) to red (x15+). | | consolePlugin() | 🚨 Console | Hooks console.error, console.warn, window.onerror and unhandledrejection; mirrors them as toasts (when active) and keeps the last 50 entries. | | a11yAuditPlugin() | ♿ A11y Audit | DOM audit for missing lang, heading skips, unlabeled inputs, empty links or buttons, duplicate ids, autoplaying media and more. Draws dashed overlays on offenders. | | a11yTabOrderPlugin() | 🔢 Tab Order | Numbers every focusable element in the order the keyboard will visit them, making tab traps and positive tabindex values obvious. | | a11yClickAuditPlugin() | 👆 Click Audit | Flags clicks that land on non-semantic elements (div, span, img) carrying click handlers but no role + tabindex. | | seoPlugin() | 🔍 SEO | Checks title length, meta description, Open Graph (ogp.me required + recommended), Twitter Card, canonical, robots, charset, viewport, hreflang and JSON-LD. | | headingsPlugin() | 🗂 Headings | Renders the h1–h6 outline. Flags level skips, empty headings, missing / multiple h1, hidden headings. Click a row to scroll + flash the element on the page. |

You can also define plugins inline — anything matching the DevLensPlugin interface works:

devlens({
  plugins: [
    {
      name: 'Info',
      icon: '📋',
      panel: () => `<div>URL: ${location.href}</div>`,
    },
  ],
})

API

devlens(config?)

Mount the panel. Only one instance can exist at a time — a second call logs a warning and returns the existing instance.

function devlens(config?: Partial<DevLensConfig>): PanelController

Config

| Field | Type | Default | Description | | --- | --- | --- | --- | | position | 'bottom-left' \| 'bottom-right' \| 'top-left' \| 'top-right' | 'bottom-right' | Panel anchor on the viewport. | | defaultOpen | boolean | false | Open the panel on first load instead of just showing the toggle. | | shortcut | string | 'ctrl+shift+d' | Keyboard shortcut — ctrl, shift, alt modifiers plus a single key. | | plugins | DevLensPlugin[] | [] | Plugins to register, in tab order. |

Returned controller

interface PanelController {
  addPlugin(plugin: DevLensPlugin): void
  removePlugin(name: string): void
  open(): void
  close(): void
  toggle(): void
  destroy(): void
}

allPlugins()

Returns a fresh instance of every built-in plugin, ready to be passed to devlens({ plugins: ... }). Use this when you want the full experience without picking plugins individually.

function allPlugins(): DevLensPlugin[]

destroyDevlens()

Tear down the current panel and release the singleton slot so devlens() can be called again.

DevLensPlugin

interface DevLensPlugin {
  name: string
  icon?: string
  panel: () => HTMLElement | string
  onMount?: (container: HTMLElement) => void
  onUnmount?: () => void
}

Lifecycle. panel() is called once each time the tab becomes active and must return the tab's content (element or HTML string). onMount(container) runs right after the content is attached, with container pointing at the wrapper element. onUnmount() runs when the user switches to another tab or the panel is destroyed — release any listeners, intervals or overlays there.

Interval convention. If your plugin uses setInterval for periodic refresh, set the id on a DOM node you own:

const id = setInterval(render, 3000)
container.setAttribute('data-interval', String(id))

The panel clears every [data-interval] descendant on unmount, so you don't have to.

Overlay convention. Any element your plugin adds to document.body should carry a data-devlens attribute. Other plugins (notably Repaints and A11y Audit) use it to ignore their own internals and avoid feedback loops.

Development

bun install
bun run dev      # Vite dev server with the playground at index.html
bun run check    # TypeScript type-check
bun run lint     # ESLint
bun run build    # library build (ESM + d.ts) to dist/

The dev/ folder holds the playground (dev/main.ts) that registers every plugin against index.html for manual testing.

Project layout

src/
  index.ts         Public entry — exports devlens(), allPlugins() and every plugin factory
  panel.ts         Panel shell, tab switching, keyboard shortcut
  styles.ts        Injected CSS for the panel chrome
  toasts.ts        Top-right toast notifications used by plugins
  types.ts         DevLensPlugin + DevLensConfig
  config.ts        Default config
  plugins/
    network.ts
    repaints.ts
    console.ts
    a11y-audit.ts
    a11y-tab-order.ts
    a11y-click-audit.ts
    seo.ts
    headings.ts

Writing a new plugin

  1. Create src/plugins/my-plugin.ts that exports a factory returning a DevLensPlugin.
  2. Export it from src/index.ts and add it to the allPlugins() helper so users who opt into everything get it for free.
  3. Register it in dev/main.ts so the playground picks it up.
  4. Follow the existing patterns: vanilla DOM, inline styles, data-devlens on overlays, data-interval on your refresh loop.

Conventions

  • Zero runtime dependencies. Keep it that way — the whole point is that DevLens drops into any project without dragging a graph behind it.
  • Vanilla DOM only. No framework, no JSX, no shadow DOM.
  • Plugins must be safe to register multiple times across hot reloads. Use localStorage / sessionStorage for any state that should survive a refresh.
  • Never throw from a plugin. Swallow errors and surface them as warnings in the tab.

License

MIT