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/overlay

v2.0.3

Published

Overlay primitives: Dialog (+ `Modal` alias), Alert Dialog, Sheet (the `Drawer` role — see below), Tooltip, Popover, Hover Card, Context Menu, Dropdown Menu, Command Palette.

Readme

@vinyasa/overlay

Overlay primitives: Dialog (+ Modal alias), Alert Dialog, Sheet (the Drawer role — see below), Tooltip, Popover, Hover Card, Context Menu, Dropdown Menu, Command Palette.

Installation

pnpm add @vinyasa/overlay @vinyasa/layout @vinyasa/typography @vinyasa/icons @vinyasa/button @vinyasa/tokens @radix-ui/react-dialog @radix-ui/react-alert-dialog @radix-ui/react-tooltip @radix-ui/react-popover @radix-ui/react-hover-card @radix-ui/react-dropdown-menu @radix-ui/react-context-menu cmdk react react-dom

Rendering requires a VinyasaProvider (from @vinyasa/tokens) above these components in the tree. None of these components render through a portal — VinyasaProvider injects theme tokens as an inline style on its own wrapper element (not :root), so anything portaled to document.body would fall outside that subtree and lose every var(--vinyasa-*) token. Each overlay's content instead uses position: fixed to float above the rest of the page while staying inside the themed subtree — the same fix already used by @vinyasa/form's Select/Combobox/Date Picker/Color Picker and @vinyasa/feedback's Toaster. This means your app must not wrap VinyasaProvider's subtree in an ancestor with its own transform/filter/perspective (which would give position: fixed descendants a new containing block other than the viewport).

This package has no root export — every component is subpath-only (import { Dialog } from '@vinyasa/overlay/dialog', never from '@vinyasa/overlay'). A root barrel re-exporting all these 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 simple, prop-driven call and a compound .Root/.Trigger/.Content/... namespace — never flat sibling exports (DialogTrigger, DialogContent, etc. don't exist; only Dialog.Trigger, Dialog.Content do). Reach for the simple call first — <Dialog title=… description=… footer=…> covers the common shape for Dialog/Sheet/AlertDialog, and <Tooltip label=…> covers Tooltip completely for almost every real use. Drop down to the compound API only for what the simple call can't express: an icon next to a title, custom body layout, more than two AlertDialog actions. An earlier single-prop <Dialog trigger= title= footer=> shape (a single flat prop bag, no compound underneath at all) was tried and dropped — a string-typed title couldn't fit an icon next to it, and there was no escape hatch once a use case outgrew the prop list. The current shape keeps the easy case easy without capping what's possible.

Dialog/Sheet/AlertDialog deliberately take no trigger prop — the caller's trigger button lives wherever it wants in the tree and just flips open (or uses defaultOpen when nothing needs to observe the state). Every anchor-based overlay is the exception: Radix has to measure and attach listeners to an actual anchor element, so the anchor has to stay structurally inside Root. Tooltip/Hover Card/Context Menu use children as that anchor directly (Context Menu wraps the whole right-clickable area, not a single trigger element); Popover/Dropdown Menu take a trigger prop instead, since their own children more naturally reads as "what's inside the popover" (<Popover trigger={<Button>Filters</Button>}><FilterForm /></Popover>). Either way, the element itself can be anything via Radix's own asChild.

Usage

Dialog

import { Dialog } from '@vinyasa/overlay/dialog';
import Button from '@vinyasa/button/button';
import Input from '@vinyasa/form/input';
import { useState } from 'react';

function EditProfileDialog() {
	const [open, setOpen] = useState(false);

	return (
		<>
			<Button onClick={() => setOpen(true)}>Edit profile</Button>
			<Dialog
				open={open}
				onOpenChange={setOpen}
				title="Edit profile"
				description="Make changes to your profile here."
				footer={
					<>
						<Button variant="outline" onClick={() => setOpen(false)}>
							Cancel
						</Button>
						<Button onClick={() => setOpen(false)}>Save changes</Button>
					</>
				}
			>
				<Input label="Name" />
			</Dialog>
		</>
	);
}
  • size picks a max-width preset: 'sm' | 'md' | 'lg' | 'xl' (defaults to 'md', 28rem).
  • A close (X) button renders in the corner automatically; pass hideCloseButton to omit it.
  • title/description/footer are ReactNode, not string — an icon next to the title works with no escape hatch needed.

Compound API, for custom body layout or an icon next to the title:

<Dialog.Root open={open} onOpenChange={setOpen}>
	<Dialog.Content size="lg">
		<Dialog.Header>
			<Dialog.Title>Edit profile</Dialog.Title>
			<Dialog.Description>Make changes to your profile here.</Dialog.Description>
		</Dialog.Header>
		<Dialog.Body>
			<Input label="Name" />
		</Dialog.Body>
		<Dialog.Footer>
			<Dialog.Close asChild>
				<Button variant="outline">Cancel</Button>
			</Dialog.Close>
			<Button>Save changes</Button>
		</Dialog.Footer>
	</Dialog.Content>
</Dialog.Root>

Dialog.Trigger asChild also works if you'd rather let Dialog own its own open state uncontrolled, instead of the open/onOpenChange pattern above.

Also available as Modal/Modal.Root/Modal.Trigger/... — Modal and Dialog are the same implementation under two names, not two components.

Alert Dialog

Same shape as Dialog, reserved for interruptions the user must resolve explicitly — a destructive or unrecoverable action. @radix-ui/react-alert-dialog disables outside-click and Escape dismissal at the primitive level, and there's no generic close button — only an explicit confirm or cancel.

import { AlertDialog } from '@vinyasa/overlay/alert-dialog';
import Button from '@vinyasa/button/button';
import { useState } from 'react';

function DeleteProjectButton() {
	const [open, setOpen] = useState(false);

	return (
		<>
			<Button variant="danger" onClick={() => setOpen(true)}>
				Delete project
			</Button>
			<AlertDialog
				open={open}
				onOpenChange={setOpen}
				title="Delete project"
				description="This cannot be undone."
				confirmLabel="Delete"
				onConfirm={() => {
					// perform the delete
				}}
			/>
		</>
	);
}
  • confirmLabel/cancelLabel default to 'Confirm'/'Cancel'.
  • onConfirm/onCancel both close the dialog automatically after running.
  • size takes the same 'sm' | 'md' | 'lg' | 'xl' preset as Dialog.

Drop to AlertDialog.Root/.Trigger/.Content/.Header/.Body/.Footer/.Title/.Description/.Action/.Cancel for a choice among more than two actions.

Sheet

Same shape as Dialog again, but edge-anchored and sliding in from that edge instead of centered and scaling in. This is the Drawer role other libraries name separately — same Radix primitive (@radix-ui/react-dialog), same edge-anchored behavior, so it isn't duplicated under a second name here.

import { Sheet } from '@vinyasa/overlay/sheet';
import Button from '@vinyasa/button/button';
import { useState } from 'react';

function FiltersSheet() {
	const [open, setOpen] = useState(false);

	return (
		<>
			<Button variant="outline" onClick={() => setOpen(true)}>
				Filters
			</Button>
			<Sheet open={open} onOpenChange={setOpen} title="Filters" side="right">
				{/* filter controls */}
			</Sheet>
		</>
	);
}
  • side picks which edge it slides in from: 'top' | 'right' | 'bottom' | 'left' (defaults to 'right').
  • Everything else — title/description/footer/hideCloseButton/compound Sheet.Root/.Trigger/... — matches Dialog exactly.

Tooltip

Needs nothing mounted anywhere else in your app first — no provider to set up near the root, unlike some other Radix-based tooltip wrappers.

import { Tooltip } from '@vinyasa/overlay/tooltip';
import Button from '@vinyasa/button/button';

<Tooltip label='Redeploys the latest commit on "main".'>
	<Button variant="outline">Redeploy</Button>
</Tooltip>;
  • children is the single anchor element the tooltip attaches hover/focus listeners to and positions against.
  • Opens after a 100ms hover delay by default — near-instant, not Radix's own 700ms default, which reads as sluggish. Override per instance with delayDuration (ms).
  • side/align/sideOffset control placement; hideArrow omits the pointer triangle.
  • aria-label on the trigger element still carries its accessible name for icon-only buttons — the tooltip is a supplementary visual hint, not a replacement for one.

Compound API, for content beyond plain text (an icon next to the label, for instance):

<Tooltip.Root>
	<Tooltip.Trigger asChild>
		<Button variant="outline">Redeploy</Button>
	</Tooltip.Trigger>
	<Tooltip.Content side="top">
		<InfoIcon aria-hidden="true" />
		Redeploys the latest commit on "main".
	</Tooltip.Content>
</Tooltip.Root>

Tooltip.Root mounts its own provider internally too — same zero-setup behavior as the simple call above. The rare case that genuinely needs one shared hover-skip-delay window across several tooltips at once (Radix's own reason a separate Provider exists) can still reach for @radix-ui/react-tooltip's own Provider directly.

Popover

import { Popover } from '@vinyasa/overlay/popover';
import Button from '@vinyasa/button/button';
import { Checkbox } from '@vinyasa/form/checkbox';

<Popover trigger={<Button variant="outline">Filters</Button>}>
	<Checkbox label="Production only" />
</Popover>;
  • trigger is required — the element the popover is anchored to and toggled by on click.
  • children is the popover's own body content.
  • side/align/sideOffset control placement; hideArrow omits the pointer triangle.

Drop to Popover.Root/.Trigger/.Anchor/.Content/.Close/.Arrow for a positioning anchor separate from the trigger, or custom content layout.

Hover Card

Same shape as Popover, but hover-revealed like Tooltip — for a richer preview (a profile card, a link summary) than a one-line tooltip label fits.

import { HoverCard } from '@vinyasa/overlay/hover-card';
import Heading from '@vinyasa/typography/heading';
import Text from '@vinyasa/typography/text';

<HoverCard trigger={<a href="/team/jamie">@jamie</a>}>
	<Heading as="h4" size="sm">
		Jamie Reyes
	</Heading>
	<Text size="sm" color="muted">
		Deployed 12 times this week
	</Text>
</HoverCard>;
  • Opens/closes after a 300ms hover delay by default — slower than Tooltip's 100ms, since a richer preview shouldn't reveal itself on every incidental hover. Override with openDelay/closeDelay (ms).
  • No provider needed, same as Tooltip, but for a different reason: Hover Card's own delay isn't shared across instances the way Tooltip's is, so there's no Provider to mount in the first place.

Dropdown Menu

import { DropdownMenu } from '@vinyasa/overlay/dropdown-menu';
import Button from '@vinyasa/button/button';

<DropdownMenu
	trigger={<Button variant="outline">Actions</Button>}
	items={[
		{ type: 'item', key: 'rename', label: 'Rename' },
		{ type: 'separator', key: 'sep' },
		{ type: 'item', key: 'delete', label: 'Delete', destructive: true },
	]}
/>;
  • items is a flat array of { type: 'item' | 'separator' | 'label', ... } entries — the common case of a flat action list.
  • An item's destructive: true styles it as a destructive action (e.g. Delete); icon/shortcut add a leading icon or trailing shortcut hint.

Checkbox items, radio items, and submenus aren't expressible via the flat items array — drop to DropdownMenu.Root/.Trigger/.Content/.Item/.CheckboxItem/.RadioGroup/.RadioItem/.Label/.Separator/.Sub/.SubTrigger/.SubContent for those.

Context Menu

Same item shape and chrome as Dropdown Menu — Radix ships near-identical primitives for both, and a context-menu item reads identically to a dropdown item, just triggered by right-click on a wrapped area instead of a click on a trigger button.

import { ContextMenu } from '@vinyasa/overlay/context-menu';

<ContextMenu items={[{ type: 'item', key: 'rename', label: 'Rename' }]}>
	<ProjectRow />
</ContextMenu>;
  • children is the area that responds to right-click — there's no separate trigger element the way Popover/Dropdown Menu have one.
  • Same compound escape hatch as Dropdown Menu: ContextMenu.Root/.Trigger/.Content/.Item/.CheckboxItem/.RadioGroup/.RadioItem/.Label/.Separator/.Sub/.SubTrigger/.SubContent.

Controlled open — reliable for closing a menu that a real right-click already opened (e.g. dismissing it if the row it's anchored to gets deleted by another collaborator in real time), not for opening one at a sensible position out of nowhere:

const [open, setOpen] = useState(false);

// e.g. inside a websocket handler:
// onRowDeleted(() => setOpen(false));

<ContextMenu open={open} onOpenChange={setOpen} items={[...]}>
	<ProjectRow />
</ContextMenu>;

Setting open to true before a real right-click/long-press has fired anchors the menu to the viewport's top-left corner instead — position comes from the pointer coordinates the trigger's own contextmenu handler captures, not from this prop (Radix's own runtime warns about this in development). There's also no defaultOpen — Radix's useControllableState call for this component hardcodes that to false regardless of what's passed. Reach for Popover/Dropdown Menu instead for a button-triggered case that needs real positioning.

Command Palette

A keyboard-driven, filterable list of grouped actions shown as a top-anchored modal (not vertically centered like Dialog) — the established convention (macOS Spotlight, VS Code's command palette). Typically opened via a global shortcut your app wires up itself; this component only renders once open/defaultOpen says so.

import { CommandPalette } from '@vinyasa/overlay/command-palette';
import { useState } from 'react';

function App() {
	const [open, setOpen] = useState(false);

	// wire `open`/`onOpenChange` to your own global Cmd+K listener

	return (
		<CommandPalette
			label="Command palette"
			placeholder="Type a command or search..."
			open={open}
			onOpenChange={setOpen}
			groups={[
				{
					key: 'actions',
					heading: 'Actions',
					items: [{ key: 'new-project', label: 'New project', shortcut: '⌘N' }],
				},
			]}
		/>
	);
}
  • label is required and never shown visually — a command palette is search-first, so it's exposed only to assistive tech as the dialog's accessible name.
  • groups is an array of { key, heading?, items } — each item is { key, label, onSelect?, disabled?, icon?, shortcut?, keywords? }.
  • Built on cmdk for filtering/keyboard nav, composed with Radix's own Dialog.Root/.Content directly (not cmdk's own Command.Dialog, which unconditionally portals into document.body — see the component's own comment on why that would break VinyasaProvider's theme scoping).

Drop to CommandPalette.Root/.Content/.Input/.List/.Group/.Item/.Separator/.Empty for anything the flat groups array can't express (async loading, a custom filter function).

Subpath imports

Every component is imported by its own subpath (e.g. @vinyasa/overlay/dialog) — there is no root @vinyasa/overlay entry, so import { X } from '@vinyasa/overlay' fails to resolve. See "This package has no root export" above for why. Also available: /alert-dialog, /sheet, /tooltip, /popover, /hover-card, /dropdown-menu, /context-menu, /command-palette, /modal.

Development

From the repository root:

pnpm --filter @vinyasa/overlay build
pnpm --filter @vinyasa/overlay test
pnpm --filter @vinyasa/overlay lint
pnpm storybook   # Overlay/*