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

@vinyasa/navigation

v2.0.3

Published

10 navigation primitives: Breadcrumbs, Pagination, Tabs, NavigationMenu, Sidebar, TopNavigation, Stepper, WorkspaceSwitcher, TreeView, CommandNavigation.

Readme

@vinyasa/navigation

10 navigation primitives: Breadcrumbs, Pagination, Tabs, NavigationMenu, Sidebar, TopNavigation, Stepper, WorkspaceSwitcher, TreeView, CommandNavigation.

Installation

pnpm add @vinyasa/navigation @vinyasa/overlay @vinyasa/icons @vinyasa/tokens @vinyasa/typography react react-dom

@vinyasa/tokens is a peer dependency: every component's styles reference the shared design-token contract, and rendering requires a VinyasaProvider (from @vinyasa/tokens) somewhere above them in the tree. @vinyasa/overlay is a peer for a narrower reason — WorkspaceSwitcher is built directly on its DropdownMenu, and CommandNavigation wraps its CommandPalette — both compose rather than reimplement. See @vinyasa/tokens's README and @vinyasa/overlay's README.

No separate stylesheet import is needed — each component's CSS is bundled into its own JS entry and loads automatically when you import it.

This package has no root export — every component is subpath-only (import { Breadcrumbs } from '@vinyasa/navigation/breadcrumbs', never from '@vinyasa/navigation'). A root barrel re-exporting all 10 components would let a bundler tree-shake the unused JS down to just the ones you import, but the CSS side-effect imports the others carry are not eligible for the same tree-shaking (confirmed empirically with both esbuild and Rollup on this monorepo's other packages). Removing the root entry entirely makes that the only possible outcome, not something that depends on your bundler being clever enough to shake it out.

Composition

Every component ships both a flat, spec-array-driven simple call (<Sidebar sections={...}>, <Tabs tabs={...}>) and a compound .Root/.Item/... namespace for anything the flat shape can't express — a custom icon mixed per item, a router Link in place of a plain href, richer per-item content. Reach for the simple call first; drop to the compound API only once a use case outgrows the spec shape.

Usage

Breadcrumbs

An accessible breadcrumb trail (<nav aria-label> + <ol>) that renders a flat label/href item list. The last item is always the current page — non-linked, regardless of whether it has an href.

import { Breadcrumbs } from '@vinyasa/navigation/breadcrumbs';

<Breadcrumbs
	items={[
		{ label: 'Acme Cloud', href: '/' },
		{ label: 'Projects', href: '/projects' },
		{ label: 'Website Redesign' },
	]}
/>;

| Prop | Type | Default | Description | | --------------- | --------------------------------------------- | -------------- | -------------------------------------------------------------- | | items | { key?; label: ReactNode; href?: string }[] | — | Required. Ordered trail. | | separator | ReactNode | chevron icon | Rendered between each pair of items. | | linkComponent | ElementType | 'a' | Swap in a router Link; receives href like a native anchor. | | label | string | 'Breadcrumb' | Accessible name for the <nav>. |

Compound parts: Breadcrumbs.Root, .List, .Item, .Link, .Page (the current, non-navigable segment), .Separator (a real <li aria-hidden>, not CSS ::before, per the WAI-ARIA breadcrumb pattern).

<Breadcrumbs.Root>
	<Breadcrumbs.List>
		<Breadcrumbs.Item>
			<Breadcrumbs.Link href="/">
				<ListIcon /> Acme Cloud
			</Breadcrumbs.Link>
		</Breadcrumbs.Item>
		<Breadcrumbs.Separator />
		<Breadcrumbs.Item>
			<Breadcrumbs.Page>2023 Q4 Report</Breadcrumbs.Page>
		</Breadcrumbs.Item>
	</Breadcrumbs.List>
</Breadcrumbs.Root>

Pagination

A page-number trail with Previous/Next controls and a sliding active-page indicator. Collapses the middle into an ellipsis once the range gets long.

import { Pagination } from '@vinyasa/navigation/pagination';
import { useState } from 'react';

function ResultsPagination() {
	const [page, setPage] = useState(1);
	return <Pagination page={page} totalPages={20} onPageChange={setPage} />;
}

| Prop | Type | Default | Description | | --------------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------- | | page | number | — | Required. Current page, 1-indexed. | | totalPages | number | — | Required. | | onPageChange | (page: number) => void | — | Required. | | siblingCount | number | 1 | Pages visible on each side of the current page. | | boundaryCount | number | 1 | Pages visible at the very start/end. | | getHref | (page: number) => string | — | When given, every page (incl. Prev/Next) renders as a real link; onPageChange still fires. |

Compound parts: .Root, .List (owns the sliding indicator via a MutationObserver watching aria-current), .Item, .Link, .Previous/.Next (pre-labeled with chevron icons), .Ellipsis. The range-computation itself is also exported standalone as getPaginationRange(page, totalPages, siblingCount?, boundaryCount?), alongside the ELLIPSIS sentinel it returns.

<Pagination
	page={page}
	totalPages={20}
	onPageChange={setPage}
	getHref={(p) => `/results?page=${p}`}
/>

Tabs

A themed wrapper around Radix's @radix-ui/react-tabs, adding a sliding active-tab indicator and a ContentGroup that stacks every panel in the same CSS grid cell (all force-mounted) so the container's height never jumps when switching tabs — it's set by the tallest panel, not whichever one happens to be active.

import { Tabs } from '@vinyasa/navigation/tabs';

<Tabs
	defaultValue="account"
	tabs={[
		{ value: 'account', label: 'Account', content: 'Update your name, email, and photo.' },
		{ value: 'billing', label: 'Billing', content: 'Manage your plan and invoices.' },
	]}
/>;

TabsProps extends Radix's own Tabs.Root props (value/defaultValue/onValueChange/orientation, fully controlled or uncontrolled) plus:

| Prop | Type | Default | Description | | ------ | ------------------------------------------------------------------------------- | ------- | ------------------------------------------- | | tabs | { value: string; label: ReactNode; content: ReactNode; disabled?: boolean }[] | — | Required. Flat value/label/content triples. |

For uneven content lengths, use the compound API with .ContentGroup so height stays stable across a switch:

<Tabs.Root defaultValue="overview">
	<Tabs.List>
		<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
		<Tabs.Trigger value="logs">Build logs</Tabs.Trigger>
	</Tabs.List>
	<Tabs.ContentGroup>
		<Tabs.Content value="overview">...</Tabs.Content>
		<Tabs.Content value="logs">...</Tabs.Content>
	</Tabs.ContentGroup>
</Tabs.Root>

NavigationMenu

A themed wrapper around Radix's @radix-ui/react-navigation-menu for a top-level nav bar whose items are either plain links or dropdown panels listing a grid of label/href/description links.

import { NavigationMenu } from '@vinyasa/navigation/navigation-menu';

<NavigationMenu
	items={[
		{
			key: 'products',
			label: 'Products',
			links: [
				{
					key: 'analytics',
					label: 'Analytics',
					href: '/analytics',
					description: 'Track usage and trends',
				},
				{
					key: 'monitoring',
					label: 'Monitoring',
					href: '/monitoring',
					description: 'Uptime and error alerts',
				},
			],
		},
		{ key: 'pricing', label: 'Pricing', href: '/pricing' },
	]}
/>;

| Prop | Type | Default | Description | | --------------- | ------------------------------------------------------------------------------------------ | ------- | --------------------------------------------------------------------- | | items | { key; label; href?; active?; links?: { key; label; href?; description?; active? }[] }[] | — | Required. Plain link when href set; dropdown grid when links set. | | linkComponent | ElementType | 'a' | Applies to every rendered link, top-level and inside dropdowns. |

value/defaultValue/onValueChange (which item's dropdown is open) pass straight through to Radix's Root. Compound parts mirror Radix 1:1: .Root, .List, .Item, .Trigger, .Link, .Content, .Indicator, .Viewport — reach for these for a custom card layout inside a dropdown instead of the plain link grid.

Sidebar

A collapsible, sectioned side-nav (<nav>) with a header/footer and labeled sections of items. Collapse state is controlled or uncontrolled, shared with descendants via context.

import { Sidebar } from '@vinyasa/navigation/sidebar';

<Sidebar
	header="Acme Cloud"
	footer="Jamie Reyes"
	sections={[
		{
			key: 'main',
			label: 'Main',
			items: [
				{ key: 'dashboard', label: 'Dashboard', icon: <ListIcon />, active: true },
				{ key: 'deployments', label: 'Deployments', icon: <CalendarIcon /> },
			],
		},
	]}
/>;

| Prop | Type | Default | Description | | ------------------- | --------------------------------------------- | ------------ | --------------------------------------------------------- | | sections | { key; label?; items: SidebarItemSpec[] }[] | — | Required. | | header / footer | ReactNode | — | Single-content header/footer slot. | | collapsible | boolean | true | Renders the built-in collapse toggle in the header. | | collapsed | boolean | uncontrolled | Controlled collapse state; pair with onCollapsedChange. | | defaultCollapsed | boolean | false | Initial state when uncontrolled. | | linkComponent | ElementType | 'a' | Used for items with href. |

SidebarItemSpec: { key; label; icon?; active?; disabled?; href?; onClick? }. When collapsed, a plain-text item label falls back to a native title attribute so meaning isn't lost on hover.

Compound parts: .Root (owns the collapse context), .Header, .Content, .Section, .Item, .Footer, .CollapseTrigger. The standalone useSidebarCollapsed() hook lets custom header/footer content elsewhere react to collapse state without prop drilling.

<Sidebar.Root>
	<Sidebar.Header>
		<strong>Acme Cloud</strong>
		<Sidebar.CollapseTrigger />
	</Sidebar.Header>
	<Sidebar.Content>
		<Sidebar.Section label="Main">
			<Sidebar.Item as="a" href="/dashboard" icon={<ListIcon />} active>
				Dashboard
			</Sidebar.Item>
		</Sidebar.Section>
	</Sidebar.Content>
</Sidebar.Root>

TopNavigation

A responsive top nav bar — brand mark, a flat row of items, an optional actions slot — with a below-md hamburger menu wired up for free.

import { TopNavigation } from '@vinyasa/navigation/top-navigation';

<TopNavigation
	brand="Acme Cloud"
	brandHref="/"
	items={[
		{ key: 'dashboard', label: 'Dashboard', active: true },
		{ key: 'deployments', label: 'Deployments' },
	]}
	actions={
		<>
			<SearchIcon />
			<UserIcon />
		</>
	}
/>;

| Prop | Type | Default | Description | | --------------------- | ------------------------- | ------------ | ------------------------------------------------------ | | items | TopNavigationItemSpec[] | — | Required. | | brand / brandHref | ReactNode / string | — | Brand mark, rendered as a link when brandHref given. | | actions | ReactNode | — | Slot for search/avatar/buttons. | | linkComponent | ElementType | 'a' | Used for the brand and any href item. | | open | boolean | uncontrolled | Controlled mobile-menu open state below md. |

The below-md breakpoint is a real @media query against the browser viewport — resize the window to see it, it can't be simulated with a fixed-width wrapper. Compound parts: .Root, .Brand, .Content (doubles as the mobile dropdown panel), .Item, .Actions, .MobileTrigger.

Stepper

A horizontal or vertical numbered step sequence (onboarding wizard, checkout flow). Each step's state (upcoming/current/completed/error) is computed from a single activeStep index.

import { Stepper } from '@vinyasa/navigation/stepper';

<Stepper
	activeStep={1}
	steps={[
		{ label: 'Account', description: 'Create your login' },
		{ label: 'Profile', description: 'Tell us about yourself' },
		{ label: 'Confirm', description: 'Review and finish' },
	]}
/>;

| Prop | Type | Default | Description | | ------------- | ----------------------------------------- | -------------- | ------------------------------------------------------------------- | | steps | { key?; label; description?; error? }[] | — | Required. error: true overrides the computed state for that step. | | activeStep | number | — | Required. 0-indexed. | | onStepClick | (index: number) => void | — | When given, every indicator becomes a clickable button. | | orientation | 'horizontal' \| 'vertical' | 'horizontal' | |

Completed/error indicators swap in check/alert-triangle icons over the step number. Compound parts: .Root, .Step, .Indicator (bundles the circle with its trailing connector line), .Content, .Label, .Description.

<Stepper
	steps={onboardingSteps}
	activeStep={activeStep}
	onStepClick={(index) => {
		if (index < activeStep) setActiveStep(index);
	}}
/>

WorkspaceSwitcher

A workspace/team-switching dropdown trigger (avatar + name + chevron), built on @vinyasa/overlay's DropdownMenu — the one component in this package whose API surface directly composes another @vinyasa/* package.

import { WorkspaceSwitcher } from '@vinyasa/navigation/workspace-switcher';
import { useState } from 'react';

function WorkspacePicker() {
	const [activeWorkspaceId, setActiveWorkspaceId] = useState('acme');
	return (
		<WorkspaceSwitcher
			workspaces={[
				{ id: 'acme', name: 'Acme Cloud', description: 'Pro plan · 12 members' },
				{ id: 'globex', name: 'Globex Corp', description: 'Free plan · 3 members' },
			]}
			activeWorkspaceId={activeWorkspaceId}
			onWorkspaceChange={setActiveWorkspaceId}
			onCreateWorkspace={() => {
				/* open create-workspace flow */
			}}
		/>
	);
}

| Prop | Type | Default | Description | | ---------------------- | ---------------------- | -------------------- | -------------------------------------------------------------------------- | | workspaces | WorkspaceSpec[] | — | Required. { id; name; description?; avatar? }. Renders nothing if empty. | | activeWorkspaceId | string | — | Required. Falls back to workspaces[0] if it matches nothing. | | onWorkspaceChange | (id: string) => void | — | Required. | | onCreateWorkspace | () => void | — | Renders a "Create workspace" item below a separator when given. | | createWorkspaceLabel | ReactNode | 'Create workspace' | |

Compound building blocks: .Trigger, .Item, .Avatar (renders workspace.avatar or falls back to name initials). The package also exports avatarImage, a vanilla-extract class string for a custom <img className={avatarImage}> avatar.

TreeView

An accessible, keyboard-navigable hierarchical tree (role="tree") — a file explorer or nested nav — with roving tabindex and controlled or uncontrolled expansion/selection.

import { TreeView, type TreeViewNodeSpec } from '@vinyasa/navigation/tree-view';
import { useState } from 'react';

const fileTree: TreeViewNodeSpec[] = [
	{
		id: 'src',
		label: 'src',
		children: [
			{ id: 'components', label: 'components', children: [{ id: 'button', label: 'Button.tsx' }] },
			{ id: 'index', label: 'index.ts' },
		],
	},
	{ id: 'package', label: 'package.json' },
];

function FileExplorer() {
	const [selectedId, setSelectedId] = useState<string>();
	return <TreeView items={fileTree} selectedId={selectedId} onSelect={setSelectedId} />;
}

| Prop | Type | Default | Description | | -------------------- | ---------------------- | ------------ | ------------------------------------------------------------------ | | items | TreeViewNodeSpec[] | — | Required. Recursive: { id; label; icon?; disabled?; children? }. | | selectedId | string | — | | | onSelect | (id: string) => void | — | Fires on click and Enter/Space. | | expandedIds | string[] | uncontrolled | Controlled expansion. | | defaultExpandedIds | string[] | [] | Initial expansion when uncontrolled. |

Full WAI-ARIA tree keyboard support: ArrowUp/ArrowDown move focus, ArrowRight expands or moves into the first child, ArrowLeft collapses or moves to the parent, Home/End jump to the first/last visible item, Enter/Space select. Collapsed nodes' children aren't rendered at all — not CSS-hidden. Compound parts: .Root, .Group (nested <ul role="group">), .Item.

CommandNavigation

An opinionated, batteries-included ⌘K/Ctrl+K launcher for jumping to pages — wraps @vinyasa/overlay's CommandPalette and adds an href-aware item shape plus its own global shortcut listener.

import { CommandNavigation } from '@vinyasa/navigation/command-navigation';
import { useState } from 'react';

function AppHeader() {
	const [open, setOpen] = useState(false);
	return (
		<>
			<CommandNavigation.Trigger onClick={() => setOpen(true)} />
			<CommandNavigation
				open={open}
				onOpenChange={setOpen}
				groups={[
					{
						key: 'pages',
						heading: 'Pages',
						items: [
							{ key: 'dashboard', label: 'Dashboard', href: '/dashboard', icon: <ListIcon /> },
							{ key: 'settings', label: 'Settings', href: '/settings', icon: <SettingsIcon /> },
						],
					},
					{
						key: 'actions',
						heading: 'Actions',
						items: [
							{
								key: 'new-project',
								label: 'Create new project',
								icon: <PlusIcon />,
								shortcut: '⌘N',
								onSelect: () => {},
							},
						],
					},
				]}
				onNavigate={(href) => router.push(href)}
			/>
		</>
	);
}

| Prop | Type | Default | Description | | ----------------- | ------------------------------ | ------------------------ | ------------------------------------------------------------------ | | groups | CommandNavigationGroupSpec[] | — | Required. { key; heading?; items: CommandNavigationItemSpec[] }. | | onNavigate | (href: string) => void | window.location.assign | Wire to your router's navigate function. | | disableShortcut | boolean | false | Disables the built-in ⌘K/Ctrl+K listener. | | open | boolean | uncontrolled | Controlled open state. |

CommandNavigationItemSpec: { key; label; href?; onSelect?; disabled?; icon?; shortcut?; keywords? } — onSelect (a non-navigation action) takes priority over href when both are given. .Trigger defaults its visible shortcut label to a platform-neutral "⌘K" rather than sniffing navigator.platform, avoiding an SSR/client mismatch — pass disableShortcut and drive open entirely from your own trigger if you need different behavior.

Subpath imports

Every component is imported by its own subpath (e.g. @vinyasa/navigation/sidebar) — there is no root @vinyasa/navigation entry, so import { X } from '@vinyasa/navigation' fails to resolve. See "This package has no root export" above for why.

Development

From the repository root:

pnpm --filter @vinyasa/navigation build
pnpm --filter @vinyasa/navigation test
pnpm --filter @vinyasa/navigation lint
pnpm storybook   # Navigation/<ComponentName>