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

@xyz/navigation-toolkit

v0.1.7

Published

Renderless, UI-agnostic navigation toolkit for Nuxt, with key-based actions and optional i18n.

Downloads

16

Readme

@xyz/navigation-toolkit

Renderless, UI-agnostic navigation toolkit for Nuxt, focused on DX, performance, and portability.
One flat list per context, key based actions by default, optional i18n at the edges.

Features

  • Single list per context, simpler mental model
  • Key based actions out of the box, clickId then actionId then key
  • Renderless component that works with any UI library
  • Optional i18n, pass t and localePath only where you render
  • SSR safe store on nuxtApp, no serialization issues
  • Nuxt module options, enabled, contexts, componentPrefix
  • Types exported for clean TypeScript integration

Install

Add the module to your Nuxt app.

# when published
pnpm add @xyz/navigation-toolkit

Enable it in nuxt.config.ts.

export default defineNuxtConfig({
  modules: ['@xyz/navigation-toolkit'],
  'navigation-toolkit': {
    enabled: true,                         // set false to make the store a no-op
    contexts: ['sidebar','header','quickAction','settings'], // optional DX hints
    componentPrefix: ''                    // for example 'X' gives <XNavItems />
  }
})

Types

Import types via a stable path, or a virtual alias.

// path export
import type { NavItem, NormalizedItem, Ctx } from '@xyz/navigation-toolkit/types'

// virtual alias
import type { NavItem, NormalizedItem } from '#nav-toolkit'

Core concepts

  • Register NavItem data into named contexts, for example sidebar, header, quickAction.
  • Register click handlers by id once. Click resolution tries clickId, then actionId, then key.
  • Render with <NavItems> which returns items, plus helpers for label, href, and onClick.
  • i18n is optional. If t and localePath are provided, labels and links localize at render time. If not, labels fall back to item.label or a humanized key, and links fall back to item.to.

API

useNav()

The store lives on nuxtApp. All methods are safe on server and client.

const nav = useNav()

nav.add(items)                               // NavItem | NavItem[]
nav.on('qa.new', fn)                         // register handler by id
nav.onKey('qa.logout', fn)                   // sugar for id === key
nav.attach(i => i.key.startsWith('qa.'), 'qa:shared') // assign a shared clickId by predicate
nav.off('qa.new')                            // remove handler
nav.clear()                                  // clear all contexts
nav.clear('sidebar')                         // clear one context

const sidebarItems = nav.items('sidebar').value // NormalizedItem[]
const has = nav.has('qa.new')                // boolean
nav.exec('qa.new', { item, event })          // manual invoke if needed

Click handler signature

type ClickHandler = (ctx: { item: NormalizedItem, event: MouseEvent }) => void

<NavItems /> renderless

Props
ctx string, required
closeOnClick boolean, optional
t optional translate function
localePath optional route localizer

Default slot receives

{
  items: NormalizedItem[],
  label: (it) => string,       // i18n aware
  href: (it) => string,        // locale aware
  onClick: (e, it) => void     // resolves action id and executes handler
}

Data model

export interface NavItem {
  key: string
  label?: string
  params?: Record<string, unknown>
  ctx: string | string[]
  to?: unknown
  icon?: string
  target?: string
  position?: 'start' | 'end' | number
  priority?: number
  disabled?: boolean
  shortcut?: string | string[]
  children?: NavItem[]
  clickId?: string
  actionId?: string
  onSelect?: (e: MouseEvent) => void
}

Notes
position orders items, start is first, end is last, numbers sort in between.
priority is a tiebreaker within the same position.
Children are nested items inside the same context.

Examples, Nuxt UI v3

Quick actions with UDropdownMenu (use onSelect)

<template>
  <NavItems ctx="quickAction" v-slot="{ items, label, onClick }">
    <UDropdownMenu
      :items="[ items.map(it => ({
        label: label(it),
        icon: it.icon,
        onSelect: (e: Event) => onClick(e as unknown as MouseEvent, it)
      })) ]"
    >
      <UButton icon="i-lucide-command" label="Commands" color="neutral" variant="outline" />
    </UDropdownMenu>
  </NavItems>
</template>

Sidebar with UNavigationMenu inside USlideover (use onClick for links)

<NavItems ctx="sidebar" :t="t" :localePath="localePath" v-slot="{ items, label, href, onClick }">
  <UNavigationMenu
    orientation="vertical"
    :items="items.map(it => ({
      label: label(it),
      icon: it.icon,
      to: href(it),
      type: 'link',
      onClick: (e: MouseEvent) => onClick(e, it),
      children: (it.children || []).map(c => ({
        label: label(c),
        icon: c.icon,
        to: href(c),
        onClick: (e: MouseEvent) => onClick(e, c)
      }))
    }))"
  />
</NavItems>

Flat picker with USelectMenu

<NavItems ctx="sidebar" v-slot="{ items, label, onClick }">
  <USelectMenu
    v-model="selected"
    :items="items.map(it => ({ label: label(it), value: it.key, icon: it.icon }))"
    searchable
    @update:model-value="(val) => {
      const it = items.find(i => i.key === val?.value)
      if (it) onClick(new MouseEvent('click'), it)
    }"
  />
</NavItems>

i18n, optional

Pass :t="t" and :localePath="localePath" to <NavItems>.
If omitted, label(it) falls back to item.label or a humanized key, href(it) falls back to item.to.

Module options at runtime

Options are exposed at useRuntimeConfig().public['navigation-toolkit'].

{
  enabled: boolean,                  // default true
  contexts: string[],                // default ['sidebar','header','footer','topbar','breadcrumb','quickAction','settings']
  componentPrefix: string            // optional, empty string by default
}

If enabled is false, useNav() returns a no-op store with the same API.
If contexts is set, adding an item to an unknown context logs a warning in dev.

Usage, end to end

// register items
const nav = useNav()
nav.add([
  { key: 'nav.home', label: 'Home', to: '/', ctx: ['sidebar','header'], position: 'start' },
  { key: 'nav.settings', label: 'Settings', to: '/settings', ctx: 'sidebar', children: [
    { key: 'nav.settings.profile', label: 'Profile', to: '/settings/profile', ctx: 'sidebar' }
  ]},
  { key: 'qa.billing', label: 'Billing', to: '/billing', ctx: 'quickAction' }
])

// register handlers by key, no attach needed
nav.on('qa.billing', ({ event }) => { event.preventDefault(); navigateTo('/billing') })
nav.on('nav.settings.profile', ({ event }) => { event.preventDefault(); /* open pane */ })

Build and publish

This module uses @nuxt/module-builder.

# from module root
rm -rf dist
pnpm prepack          # runs nuxt-module-build build
# then publish
pnpm publish --access public

Ensure tsconfig.json sets "declarationMap": false, this avoids sourcemap JSON being mistaken for declarations during DTS bundling.

License

MIT


FAQ

Do I need attach if I register handlers by key
No for common cases. Use attach when a subset should share a different handler id than the key, for example a feature flag or a tenant override.

What happens if I reuse the same key in multiple contexts
The same handler fires for all those items. If you need context specific behavior, set clickId per item, for example billing:sidebar and billing:header, or use attach(i => i.ctx === 'sidebar' && i.key === 'qa.billing', 'billing:sidebar').

Can I skip the label and href helpers
Yes. They centralize i18n and link rules. If you do not use i18n or localized routes, read item.label and item.to directly. Keeping the helpers gives you a single extension point for later.