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

@yoshihisak/navijs

v0.2.3

Published

Framework-agnostic, type-safe in-app tour & onboarding library with Smart Locator.

Readme

navijs

npm CI bundle size types license

Framework-agnostic, type-safe in-app tour & onboarding library — with a Smart Locator that doesn't break when your DOM does.

▶ Live demo · npm · docs

npm install @yoshihisak/navijs
  • ✅ Zero runtime dependencies
  • ✅ ESM + CJS + TypeScript types
  • ✅ Works with React / Vue / Angular / vanilla — anything that renders DOM
  • ✅ Smart Locator combines text / role / aria / testid / selector / xpath as AND constraints
  • ✅ Cross-page resume via localStorage
  • ✅ Spotlight + tooltip out of the box, 4 built-in theme presets
  • ✅ Shadow DOM piercing — works inside Lit / Stencil / web component apps
  • ✅ a11y: focus trap, aria-live announcements, focus restore

Quickstart

import { createGuide, locator } from "@yoshihisak/navijs";

const tour = createGuide({
  id: "first-run",
  // CSP が厳しい環境では nonce を渡すか、injectStyles:false + 自前CSSにする
  // styleNonce: "...",
  // injectStyles: false,
  // 調査用: locator解決時間などを console.debug に出す
  // debug: true, // or "verbose"
});

tour
  .addStep({
    target: locator().byTestId("search-input"),
    title: "検索",
    body: "ここからキーワード検索できます。",
    placement: "bottom",
  })
  .addStep({
    target: locator().byText("請求書を作成").byRole("button"),
    body: "ここから新規作成できます。",
    placement: "top",
    url: /\/invoices/, // 別ページに該当するステップは URL マッチを待つ
  })
  .addStep({
    target: locator().byAriaLabel("open-help"),
    body: "困ったらここから。",
  });

tour.on("complete", () => console.log("done!"));
tour.start();

CDN (no build step)

<script src="https://unpkg.com/@yoshihisak/navijs/dist/navijs.global.js"></script>
<script>
  const tour = navijs.createGuide({ id: "first-run" });
  tour
    .addStep({
      target: navijs.locator().byTestId("nav-home"),
      title: "ホーム",
      body: "ここからダッシュボードへ。",
    })
    .addStep({
      target: navijs.locator().byRole("button", { name: /create/i }),
      body: "新規作成はここから。",
    });
  tour.start();
</script>

The IIFE bundle is 25 KB minified / 8.3 KB gzipped, fully self-contained (styles inject at runtime), and exposes window.navijs. Same createGuide / locator / events / Smart Locator as the npm build. Available on unpkg and jsDelivr.

React

import { useGuide, locator } from "@yoshihisak/navijs/react";

function App() {
  const tour = useGuide({
    id: "first-run",
    define: (g) => {
      g.addStep({
        target: locator().byTestId("nav-home"),
        title: "ホーム",
        body: "ここからダッシュボードへ。",
      });
      g.addStep({
        target: locator().byRole("button", { name: /create/i }),
        body: "新規作成はここから。",
      });
    },
  });

  return (
    <button onClick={() => tour.start()} disabled={tour.isActive}>
      Start tour ({tour.currentStep + 1} / {tour.totalSteps || "?"})
    </button>
  );
}

useGuidestart / next / prev / close / reset のコールバックと、 isActive / currentStep / totalSteps / isCompleted の reactive state を返す。 ガイドインスタンスは id が変わらない限り再生成されないので、進捗が保持される。

Why Smart Locator

CSS selectors break when class names get hashed. XPath breaks when JSX inserts a <Fragment> somewhere. data-testid may be tree-shaken in production. navijs lets you stack multiple signals:

locator()
  .byText("請求書を作成")        // visible text
  .byRole("button")              // semantic role
  .byTestId("create-invoice")    // explicit test id
  .bySelector("[data-cy=create-invoice]")
  .byXPath("//button[contains(., '請求書')]")
  .fallback(locator().bySelector("#legacy-create"))
  .timeout(8000);                // wait for the element to appear

Strategies on the same chain are evaluated as AND — every signal must agree. If the chain returns zero matches, the fallback chain is tried. See docs/SMART_LOCATOR.md.

Compared to other tour libraries

| | navijs | intro.js | react-joyride | @reactour/tour | shepherd.js | | --- | --- | --- | --- | --- | --- | | Bundle (min / gzip) † | 25 KB / 8.3 KB | 62 / 17 | 80 / 27 ‡ | 29 / 10 ‡ | 41 / 14 | | License | MIT | AGPLv3 (paid commercial) | MIT | MIT | MIT | | Framework | any (React adapter built-in) | jQuery-era, ad-hoc React | React only | React only | any (jQuery-era) | | TypeScript | first-class | @types/intro.js | typed | typed | typed | | Selector strategy | multi-signal AND + fallback + timeout | data-intro attrs on DOM | string class/id | string class/id | string class/id | | Wait for element | built-in (MutationObserver) | n/a | n/a | n/a | manual | | Cross-page resume | built-in (localStorage) | n/a | manual | manual | manual | | Custom render slot | Step.render (full chrome) | CSS only | render props | render props | template strings | | CDN <script> build | yes | yes | n/a | n/a | yes |

† Measured 2026-04-27 with esbuild --minify --format=iife --target=es2018. React peers excluded for React-only libs. ‡ Excludes react / react-dom (peer deps). navijs row is core only; the React adapter adds ~2 KB ESM unminified.

navijs is the smallest of the bunch and the only one that doesn't make you decorate the DOM you're touring.

Smart Locator vs XPath-only

Most React tour libraries ask you to either decorate every target with an id / data-testid, or to copy an absolute XPath out of DevTools. Both options break the moment the DOM moves:

| Concern | XPath only (e.g. //div[2]/section/button[1]) | Smart Locator | | --- | --- | --- | | Class names hashed by CSS Modules / Tailwind JIT | depends — XPath dodges class names | works (uses role / text / testid) | | <Fragment> inserted/removed during render | breaks — sibling indices shift | survives — text + role still match | | A11y refactor renames a <div role="button"> to <button> | breaks | survives — role normalizes both | | data-testid stripped in production | n/a | falls through to text + role | | Target hasn't mounted yet (async route, lazy data) | needs custom polling | .timeout(ms) built-in via MutationObserver | | Target legitimately moved to a new component | breaks silently | .fallback() chain catches it | | Two matches on the page (e.g. duplicated CTA in header + body) | first match wins blindly | ranked by visibility → viewport → DOM depth |

The point isn't that XPath is bad — it's that a single signal is fragile by definition. Smart Locator lets you stack as many signals as you want, evaluated as AND, so any one of them breaking still leaves the lookup pinned by the others. One signal works too; you only add more when stability matters.

// Brittle: one signal, no fallback, no wait.
target: "xpath=/html/body/div[2]/main/section[3]/button[1]"

// Resilient: three signals + fallback + timeout.
target: locator()
  .byRole("button", { name: /create invoice/i })
  .byTestId("create-invoice")
  .fallback(locator().bySelector("#legacy-create"))
  .timeout(8000)

Docs

Run the demo

npm install
npm run demo

Roadmap

| Version | Plan | | --- | --- | | v0.1 (current) | Core: createGuide / Smart Locator / spotlight + tooltip / localStorage resume / navijs/react hook / Step.render override / CDN UMD build / theme presets / a11y polish / Shadow DOM piercing | | v0.2 | iframe traversal, near() / nthOf() locator extensions | | v0.3 | Dedicated @navijs/react package (JSX-as-step API) | | v0.4 | @navijs/vue | | v1.0 | No-code editor + AI tour generator (SaaS) |

Contributing

See CONTRIBUTING.md for local development, test conventions, and the release flow. Bug reports and PRs welcome.

License

MIT