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

@asafarim/dd-menu

v1.5.0

Published

A minimal, elegant, and highly customizable dropdown menu React component. Perfect for navbar, sidebar, or any dropdown needs with beautiful theming and smooth animations.

Downloads

233

Readme

@asafarim/dd-menu

npm version TypeScript License: MIT

A powerful, customizable dropdown menu and searchable dropdown component library for React with TypeScript. Features recursive nesting, multiple themes, custom triggers, keyboard navigation, and accessibility support. See a live demo at alisafari-it.github.io for different use cases.

DD Menu Showcase

Components

DDMenu - Dropdown Menu

A flexible dropdown menu component with support for nested items, custom triggers, and multiple variants.

DDSearchable - Searchable Dropdown

A powerful searchable dropdown component with filtering, keyboard navigation, and customizable search behavior.

DDMenuWrapper - Style Isolation Container

A wrapper component that creates an isolated styling context for DDMenu components, preventing external styles from affecting the dropdown's appearance and positioning.

Features

  • 🔄 Recursive Nesting: Create unlimited nested dropdown menus
  • 🔍 Searchable Dropdown: Filter items with real-time search and keyboard navigation
  • 🎨 Multiple Variants: Choose from default, minimal, outlined, filled, navbar, and sidebar styles
  • 🌓 Theme Support: Built-in light, dark, and auto themes
  • 📱 Responsive: Works perfectly on all device sizes
  • Accessible: Full keyboard navigation and ARIA support
  • 🔧 Highly Customizable: Custom triggers, icons, styling, and search configuration
  • 🛡️ Style Isolation: Prevent external CSS from interfering with dropdown styling
  • Performance: Debounced search, virtual scrolling support, and optimized rendering
  • 🧩 Zero Dependencies: Pure React and CSS implementation

Installation

# Using npm
npm install @asafarim/dd-menu

# Using yarn
yarn add @asafarim/dd-menu

# Using pnpm
pnpm add @asafarim/dd-menu

Quick Start

import DDMenu, { DDSearchable, DDMenuWrapper, MenuItem } from "@asafarim/dd-menu";

const App = () => {
  const menuItems: MenuItem[] = [
    { id: "home", label: "Home", link: "/", icon: "🏠" },
    {
      id: "products",
      label: "Products",
      icon: "📦",
      children: [
        { id: "electronics", label: "Electronics", link: "/products/electronics" },
        { id: "clothing", label: "Clothing", link: "/products/clothing" },
      ],
    },
    { id: "about", label: "About Us", link: "/about", icon: "ℹ️" },
  ];

  return (
    <div>
      {/* Dropdown Menu */}
      <DDMenu 
        items={menuItems} 
        theme="auto" 
        variant="default" 
        size="md" 
        placement="bottom-start"
        closeOnClick={true}
        onItemClick={(item) => console.log('Clicked:', item)}
      />

      {/* Searchable Dropdown */}
      <DDSearchable
        items={menuItems}
        placeholder="Search items..."
        onItemSelect={(item) => console.log('Selected:', item)}
        theme="auto"
        size="md"
      />
    </div>
  );
};

Theming

Both DDMenu and DDSearchable support theme="light" | "dark" | "auto".

  • Root theme class: the root element receives dd-menu--{theme} / dd-searchable--{theme}.
  • Auto mode: respects OS preference via prefers-color-scheme. If your app sets a theme attribute on a parent (e.g. <html data-theme="light">), it overrides auto so the app choice wins.
  • CSS variables: colors are driven by CSS custom properties like --dd-bg, --dd-text, --dd-border, etc.

If you have a global theme toggle, set data-theme="light" or data-theme="dark" on html or body so all menus follow your app’s theme while using theme="auto".

DDMenu Usage

Basic Dropdown Menu

<DDMenu
  items={menuItems}
  variant="default"
  onItemClick={(item) => console.log(item)}
/>

Custom Trigger

const customTrigger = (
  <button className="my-button">
    Action Menu
  </button>
);

<DDMenu
  items={menuItems}
  variant="minimal"
  trigger={customTrigger}
  onItemClick={(item) => console.log(item)}
/>

Navbar Profile Menu

const profileTrigger = (
  <div className="profile-trigger">
    <div className="avatar">JD</div>
    <span>John Doe</span>
  </div>
);

<DDMenu
  items={profileMenuItems}
  variant="navbar"
  trigger={profileTrigger}
  placement="bottom-end"
  onItemClick={handleProfileAction}
/>

Using DDMenuWrapper for Style Isolation

When using DDMenu in complex layouts like navbars where external styles might interfere with the dropdown's appearance or positioning, wrap it with DDMenuWrapper to create an isolated styling context:

<nav className="main-navbar">
  <div className="logo">Brand</div>
  
  {/* Isolate DDMenu from navbar styles */}
  <DDMenuWrapper>
    <DDMenu
      items={menuItems}
      variant="navbar"
      trigger={<span>Products</span>}
      placement="bottom-start"
    />
  </DDMenuWrapper>
  
  <a href="/about">About</a>
  <a href="/contact">Contact</a>
</nav>

Sidebar Navigation

const sidebarItems = [
  { id: "dashboard", label: "Dashboard", icon: "📊" },
  {
    id: "products",
    label: "Products",
    icon: "📦",
    children: [
      { id: "list", label: "Product List" },
      { id: "add", label: "Add Product" }
    ]
  }
];

<DDMenu
  items={sidebarItems}
  variant="sidebar"
  onItemClick={handleNavigation}
/>

DDSearchable Usage

Basic Searchable Dropdown

<DDSearchable
  items={menuItems}
  placeholder="Search items..."
  onItemSelect={(item) => console.log('Selected:', item)}
/>

Different Variants

<DDSearchable variant="default" items={items} />
<DDSearchable variant="minimal" items={items} />
<DDSearchable variant="outlined" items={items} />
<DDSearchable variant="filled" items={items} />

Advanced Search Configuration

<DDSearchable
  items={items}
  caseSensitive={true}
  minSearchLength={2}
  debounceMs={500}
  searchKeys={["label", "id"]}
  onSearchChange={(term) => console.log("Search:", term)}
/>

Allow Custom Values

<DDSearchable
  items={items}
  allowCustomValue={true}
  onCustomValue={(value) => {
    // Handle custom value
    console.log('Custom value:', value);
    // You could add it to your items list
  }}
  placeholder="Type anything..."
/>

Controlled Component

const [selectedItem, setSelectedItem] = useState(null);

<DDSearchable
  items={items}
  selectedItem={selectedItem}
  onItemSelect={setSelectedItem}
  clearable={true}
/>

Showcase Examples

Navigation Menu

Navbar Dropdown

<DDMenu
  items={navMenuItems}
  className="dd-menu--navbar"
  placement="bottom"
  closeOnClick={true}
  size="lg"
  theme="auto"
  trigger={
    <div className="dd-menu__trigger dd-menu__trigger--text">
      Navigation
    </div>
  }
/>

User Profile Menu

User Profile Dropdown

<DDMenu
  items={profileMenuItems}
  className="dd-menu--navbar"
  trigger={
    <div className="user-profile-trigger">
      <div className="avatar">JD</div>
      <span>John Doe</span>
    </div>
  }
  placement="bottom-end"
/>

Custom Button Trigger

<DDMenu 
  items={navMenuItems} 
  className="dd-menu--minimal"
  trigger={
    <button className="action-button">
      Action Menu
    </button>
  }
/>

API Reference

DDMenuWrapper Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | Required | The DDMenu component to wrap | | className | string | '' | Additional CSS class names | | style | CSSProperties | {} | Inline styles |

DDMenu Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | items | MenuItem[] | Required | Array of menu items | | theme | 'light' \| 'dark' \| 'auto' | 'auto' | Theme for this menu | | variant | 'default' \| 'minimal' \| 'navbar' \| 'sidebar' | 'default' | Visual variant | | size | 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl' | 'md' | Font sizing preset | | placement | 'bottom' \| 'bottom-start' \| 'bottom-end' \| 'top' \| 'top-start' \| 'top-end' \| 'right' \| 'left' | 'bottom-start' | Dropdown placement | | closeOnClick | boolean | true | Close menu when an item is clicked | | hoverDelay | number | 150 | Delay (ms) for opening submenus on hover | | trigger | ReactNode | Default button | Custom trigger element | | onItemClick | (item: MenuItem) => void | undefined | Item click callback | | onHoverChange | (isHovering: boolean) => void | undefined | Hover state callback | | onFontSizeChange | (size: DDMenuSize) => void | undefined | Emits internal font size | | className | string | '' | Additional CSS class names | | style | CSSProperties | {} | Inline styles |

MenuItem Type

type MenuItem = {
  id: string;
  label: string;
  link?: string;
  icon?: string | ReactNode;
  onClick?: () => void;
  disabled?: boolean;
  children?: MenuItem[];
};

DDSearchable Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | items | MenuItem[] | Required | Items to search (supports nesting) | | theme | 'light' \| 'dark' \| 'auto' | 'auto' | Component theme | | variant | 'default' \| 'minimal' \| 'outlined' \| 'filled' | 'default' | Visual variant | | size | 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl' | 'md' | Font sizing preset | | placeholder | string | 'Search...' | Input placeholder | | onItemSelect | (item: MenuItem) => void | undefined | Fired when an item is selected | | onSearchChange | (term: string) => void | undefined | Fired on input changes (debounced) | | disabled | boolean | false | Disable the control | | clearable | boolean | true | Show a clear button | | maxHeight | number | 300 | Max dropdown height (px) | | noResultsText | string | 'No results found' | Empty state label | | searchKeys | (keyof MenuItem)[] | ['label','id'] | Fields used for matching | | caseSensitive | boolean | false | Case sensitivity for matching | | minSearchLength | number | 0 | Minimum length to start filtering | | debounceMs | number | 300 | Debounce time for input changes | | selectedItem | MenuItem \| null | null | Controlled selected item | | allowCustomValue | boolean | false | Allow arbitrary values | | onCustomValue | (value: string) => void | undefined | Called when a custom value is chosen |

Styling

CSS Variables

The components expose CSS variables you can override at any scope:

:root {
  --dd-bg: #ffffff;
  --dd-text: #111827;
  --dd-border: #e5e7eb;
  --dd-bg-hover: #f9fafb;
  --dd-bg-active: #f3f4f6;
  --dd-accent: #3b82f6;
}

[data-theme="dark"] {
  --dd-bg: #18181b;
  --dd-text: #ffffff;
  --dd-border: #3f3f46;
  --dd-bg-hover: #27272a;
  --dd-bg-active: #3f3f46;
  --dd-accent: #60a5fa;
}

Style Isolation

When integrating DDMenu in complex layouts like navbars, you might encounter styling conflicts where external CSS affects the dropdown's appearance or positioning. The DDMenuWrapper component creates an isolated styling context that prevents these issues:

// Import the wrapper
import { DDMenu, DDMenuWrapper } from '@asafarim/dd-menu';

// Use the wrapper to isolate DDMenu from navbar styles
<DDMenuWrapper>
  <DDMenu items={items} variant="navbar" />
</DDMenuWrapper>

The wrapper:

  • Creates a new stacking context with isolation: isolate
  • Resets potentially inherited properties that could affect positioning
  • Ensures proper z-index handling
  • Re-establishes all the CSS variables needed by DDMenu

Accessibility

The dropdown menu is built with accessibility in mind:

  • Keyboard navigation (Tab, Enter, Escape, Arrow keys)
  • ARIA attributes for screen readers
  • Focus management for keyboard users
  • Proper contrast ratios for text

License

MIT © Ali Safari