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

@manhphi1309/dialog

v0.3.29

Published

A fully-accessible dialog package built on top of [Radix UI Dialog](https://www.radix-ui.com/primitives/docs/components/dialog). Ships two complete component families:

Readme

@manhphi1309/dialog

A fully-accessible dialog package built on top of Radix UI Dialog. Ships two complete component families:

  • Dialog* — standard modal dialog, always renders as a centered overlay
  • ResponsiveDialog* — adaptive wrapper that switches to a bottom Drawer on mobile screens (< 768 px)

Installation

npm install @manhphi1309/dialog

Dependencies

| Package | Role | | --------------------- | --------------------------------------------- | | @manhphi1309/button | Close / action buttons inside dialogs | | @manhphi1309/drawer | Mobile fallback used by ResponsiveDialog* | | @manhphi1309/hooks | useIsMobile() for the responsive breakpoint | | @manhphi1309/utils | cn() class-name helper |


Dialog Family

A set of composable primitives that always render as a centred modal overlay, regardless of screen size.

Dialog

The root component. Controls open/close state and provides context to all children. Thin wrapper around Radix Dialog.Root.

<Dialog open={open} onOpenChange={setOpen}>
  ...
</Dialog>

| Prop | Type | Notes | | -------------- | ------------------------- | -------------------------------------------------------------- | | open | boolean | Controlled open state | | onOpenChange | (open: boolean) => void | Called when open state changes | | defaultOpen | boolean | Uncontrolled initial state | | modal | boolean | Default true — traps focus and blocks background interaction |


DialogTrigger

Wraps the element that opens the dialog when clicked. Passes through all props to the underlying Radix Dialog.Trigger. Use asChild to compose with your own element.

<DialogTrigger asChild>
  <Button>Open</Button>
</DialogTrigger>

DialogPortal

Renders its children into a portal outside the current DOM tree (appended to document.body). Ensures the dialog appears above all other content and avoids z-index / overflow issues.

Usually you do not use this directly — DialogContent includes it automatically.


DialogOverlay

The backdrop behind the dialog content. Renders a fixed, full-screen semi-transparent layer (bg-black/10) with a backdrop blur on supported browsers.

Animations:

  • Open → fade-in-0
  • Close → fade-out-0

Usually you do not use this directly — DialogContent includes it automatically.


DialogContent

The visible panel of the dialog. Renders centred on screen (top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2), inside a portal with an overlay underneath. Comes with a ✕ ghost button in the top-right corner by default.

Animations:

  • Open → fade-in-0 + zoom-in-95
  • Close → fade-out-0 + zoom-out-95

| Prop | Type | Default | Notes | | ----------------- | --------- | ------- | ------------------------------------------------------- | | showCloseButton | boolean | true | Show/hide the built-in ✕ button in the top-right corner | | className | string | — | Extra classes merged on the content panel |

<DialogContent showCloseButton={false}>...</DialogContent>

Multi-Step Wizard Mode

If you pass the panels prop, DialogContent transforms into a multi-step sliding wizard with animated transitions.

| Prop | Type | Description | | :------------------- | :------------------------ | :------------------------------------------------------------------------ | | panels | React.ReactNode[] | An array of panel components to render in the sliding track. | | activePanelIndex | number | (Controlled mode) The index of the currently active panel. | | onPanelIndexChange | (index: number) => void | (Controlled mode) Callback fired when a navigation hook triggers a slide. |

Internal State Management

If you don't provide activePanelIndex, the dialog will manage the sliding state internally. You can access the navigation controls from within your panel components using the useDialogPanel hook:

import { useDialogPanel } from "@manhphi1309/dialog"

function MyPanel() {
  const { nextPanel, prevPanel, activePanelIndex, setPanelIndex } =
    useDialogPanel()

  return <Button onClick={nextPanel}>Go to next step</Button>
}

DialogHeader

A layout wrapper for the top section of a dialog. Stacks children vertically with gap-2. Typically holds DialogTitle and DialogDescription.

<DialogHeader>
  <DialogTitle>Title</DialogTitle>
  <DialogDescription>Optional description.</DialogDescription>
</DialogHeader>

DialogTitle

The accessible title of the dialog. Rendered as a Radix Dialog.Title (connected to aria-labelledby on the content). Styled with text-base font-medium leading-none using the heading font.

Required for accessibility — screen readers announce this when the dialog opens.


DialogDescription

The accessible description of the dialog. Rendered as a Radix Dialog.Description (connected to aria-describedby on the content). Styled as small muted text. Links inside it are automatically underlined.

Optional but recommended for accessibility.


DialogFooter

A layout wrapper for the bottom action area. Bleeds to the edges of the content panel (-mx-4 -mb-4), adds a top border and bg-muted/50 tint. Actions stack vertically on mobile, align right horizontally on sm+.

| Prop | Type | Default | Notes | | ----------------- | --------- | ------- | -------------------------------------------------------- | | showCloseButton | boolean | false | Appends a Close outline button wired to Dialog.Close |

<DialogFooter showCloseButton>
  <Button type="submit">Save</Button>
</DialogFooter>

DialogClose

A bare close trigger primitive. Closes the dialog when activated. Use asChild to compose with a custom element.

<DialogClose asChild>
  <Button variant="ghost">Cancel</Button>
</DialogClose>

ResponsiveDialog Family

Drop-in replacements for the Dialog* components. Each one checks useIsMobile() (breakpoint: 768 px) and renders the appropriate primitive:

| Component | Desktop (≥ 768 px) | Mobile (< 768 px) | | ----------------------------- | ------------------- | ------------------- | | ResponsiveDialog | Dialog | Drawer | | ResponsiveDialogTrigger | DialogTrigger | DrawerTrigger | | ResponsiveDialogClose | DialogClose | DrawerClose | | ResponsiveDialogContent | DialogContent | DrawerContent | | ResponsiveDialogHeader | DialogHeader | DrawerHeader | | ResponsiveDialogFooter | DialogFooter | DrawerFooter | | ResponsiveDialogTitle | DialogTitle | DrawerTitle | | ResponsiveDialogDescription | DialogDescription | DrawerDescription |

All props are forwarded to the underlying component unchanged. showCloseButton on ResponsiveDialogContent and ResponsiveDialogFooter works the same way as on their dialog counterparts, including the correct close primitive for the active variant.

Note: ResponsiveDialogContent does not forward showCloseButton to DrawerContent (the Drawer handles close via the handle bar and swipe gesture). The prop is only meaningful on desktop.

Note: ResponsiveDialogFooter is hidden on mobile by default (hiddenOnMobile={true}). To display the footer on mobile, explicitly pass hiddenOnMobile={false}.

Multi-Step Wizard & useResponsiveDialogPanel

ResponsiveDialogContent also supports the panels prop for a multi-step sliding wizard. The transitions adapt automatically: horizontal slide on desktop (Dialog) and vertical/horizontal slide on mobile (Drawer).

When using uncontrolled mode, use the useResponsiveDialogPanel hook. It intelligently proxies the correct context (Dialog or Drawer) depending on the active responsive variant!

import { useResponsiveDialogPanel } from "@manhphi1309/dialog"

function MyResponsivePanel() {
  const { nextPanel } = useResponsiveDialogPanel()

  return <Button onClick={nextPanel}>Next Step</Button>
}

Drawer Snap Points (Mobile Only)

Because ResponsiveDialog forwards properties directly to the underlying Vaul <Drawer> on mobile devices, you can pass drawer-specific props like snapPoints directly to the <ResponsiveDialog> root component. The desktop <Dialog> will safely ignore them.

// Using React state to properly control the active snap point
const [snap, setSnap] = React.useState<number | string | null>("500px")

<ResponsiveDialog
  snapPoints={["300px", "500px", 1]}
  activeSnapPoint={snap}
  setActiveSnapPoint={setSnap}
  fadeFromIndex={1}
>
  ...
</ResponsiveDialog>

[!WARNING] Important Vaul Requirements:

  1. Strict Types: If you provide snapPoints, you are strictly required by Vaul to provide fadeFromIndex as a number.
  2. Controlled State: If you hardcode activeSnapPoint as a string/number prop without passing a state setter to setActiveSnapPoint, the Drawer will glitch and snap back down when the user tries to drag it, because React will force it back to the hardcoded prop value on the next render. Always use a state hook (like useState) for the active point.

Usage

Standard Dialog

import { Button } from "@manhphi1309/button"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@manhphi1309/dialog"

export default function DeleteConfirm() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="destructive">Delete account</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Are you absolutely sure?</DialogTitle>
          <DialogDescription>
            This action cannot be undone. Your account will be permanently
            deleted.
          </DialogDescription>
        </DialogHeader>
        <DialogFooter showCloseButton>
          <Button variant="destructive">Delete</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

Responsive Dialog (Dialog on desktop, Drawer on mobile)

import { Button } from "@manhphi1309/button"
import {
  ResponsiveDialog,
  ResponsiveDialogContent,
  ResponsiveDialogDescription,
  ResponsiveDialogFooter,
  ResponsiveDialogHeader,
  ResponsiveDialogTitle,
  ResponsiveDialogTrigger,
} from "@manhphi1309/dialog"

export default function EditProfile() {
  return (
    <ResponsiveDialog>
      <ResponsiveDialogTrigger asChild>
        <Button>Edit Profile</Button>
      </ResponsiveDialogTrigger>
      <ResponsiveDialogContent>
        <ResponsiveDialogHeader>
          <ResponsiveDialogTitle>Edit profile</ResponsiveDialogTitle>
          <ResponsiveDialogDescription>
            Make changes to your profile here.
          </ResponsiveDialogDescription>
        </ResponsiveDialogHeader>
        {/* form content */}
        <ResponsiveDialogFooter>
          <Button type="submit">Save changes</Button>
        </ResponsiveDialogFooter>
      </ResponsiveDialogContent>
    </ResponsiveDialog>
  )
}