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

@nobertdev/react-spotlight-search

v0.2.0

Published

A beautiful, accessible command palette / spotlight search for React apps. Cmd+K ready.

Readme

@nobertdev/react-spotlight-search

A beautiful, accessible command palette / spotlight search for React apps. Cmd+K ready.

npm version license Buy Me a Coffee



Features

  • ⌨️ Keyboard-firstCmd+K / Ctrl+K out of the box, fully navigable with arrow keys
  • Action shortcuts — each action's shortcut field registers as a real global keybinding, not just a label
  • 🔍 Fuzzy search — smart weighted ranking across label, description, and keywords
  • 🗂️ Grouped actions — organise actions under named categories
  • 🌗 Light / Dark / Auto theme — respects system preference and Tailwind's dark class
  • Accessiblerole="dialog", role="combobox", full keyboard navigation, screen-reader friendly
  • 🪶 Zero styling dependencies — styles are injected automatically; no CSS import needed
  • 🪟 Portal rendering — modal renders on document.body, avoiding z-index conflicts
  • 📦 Tiny — ~4 kb gzipped, only React as a peer dependency
  • 🔷 Fully typed — written in TypeScript with all types exported

Table of Contents


Installation

npm install @nobertdev/react-spotlight-search
# or
yarn add @nobertdev/react-spotlight-search
# or
pnpm add @nobertdev/react-spotlight-search

Peer dependencies — ensure these are already in your project:

npm install react react-dom   # React >=17 required

Quick Start

1. Wrap your app with SpotlightProvider

import { SpotlightProvider } from "@nobertdev/react-spotlight-search";

const actions = [
  {
    id: "home",
    label: "Go to Home",
    description: "Navigate to the home page",
    icon: "🏠",
    group: "Navigation",
    onSelect: () => (window.location.href = "/"),
  },
  {
    id: "dark-mode",
    label: "Toggle Dark Mode",
    description: "Switch between light and dark themes",
    icon: "🌙",
    shortcut: "⌘D",
    keywords: ["theme", "appearance", "dark", "light"],
    group: "Settings",
    onSelect: () => document.documentElement.classList.toggle("dark"),
  },
];

export default function App() {
  return (
    <SpotlightProvider actions={actions} theme="auto">
      <YourApp />
    </SpotlightProvider>
  );
}

Press Cmd+K (macOS) or Ctrl+K (Windows / Linux) to open the palette.


2. Add a trigger button

import { useSpotlightContext } from "@nobertdev/react-spotlight-search";

function NavBar() {
  const { open } = useSpotlightContext();

  return (
    <button onClick={open}>
      Search <kbd>⌘K</kbd>
    </button>
  );
}

3. Standalone usage (no provider)

Use useSpotlight to manage state yourself and render <Spotlight> directly:

import { Spotlight, useSpotlight } from "@nobertdev/react-spotlight-search";
import { createPortal } from "react-dom";

function MyApp() {
  const { isOpen, close } = useSpotlight({ shortcut: "mod+k" });

  return (
    <>
      {isOpen &&
        createPortal(
          <Spotlight actions={actions} onClose={close} />,
          document.body,
        )}
    </>
  );
}

API Reference

SpotlightProvider

Top-level provider component. Manages state, registers the keyboard shortcut, and renders the modal as a portal.

<SpotlightProvider
  actions={actions}
  shortcut="mod+k"
  theme="auto"
  placeholder="Search actions..."
  emptyMessage="No results found."
  limit={8}
  onOpen={() => console.log("opened")}
  onClose={() => console.log("closed")}
>
  <App />
</SpotlightProvider>

| Prop | Type | Default | Description | | -------------- | ----------------------------- | --------------------- | -------------------------------------------------------- | | children | React.ReactNode | — | Required. Your app content. | | actions | SpotlightAction[] | [] | Initial list of actions shown in the palette. | | shortcut | string | "mod+k" | Keyboard shortcut to open/close the palette. | | placeholder | string | "Search actions..." | Placeholder text inside the search input. | | emptyMessage | string | "No results found." | Message displayed when no results match the query. | | limit | number | 8 | Maximum number of results shown at a time. | | theme | "light" \| "dark" \| "auto" | "auto" | Colour scheme. "auto" follows system / Tailwind class. | | className | string | — | Extra CSS class applied to the overlay element. | | onOpen | () => void | — | Callback fired when the palette opens. | | onClose | () => void | — | Callback fired when the palette closes. |


Spotlight

The bare modal component. Renders the overlay, search input, and action list. Use this when you want complete manual control over open/close state.

<Spotlight
  actions={actions}
  onClose={close}
  placeholder="Search..."
  emptyMessage="Nothing found."
  limit={10}
  theme="dark"
/>

Accepts all props from SpotlightProvider except children and shortcut.

Note: <Spotlight> does not register any keyboard shortcut on its own. Use useSpotlight alongside it to add shortcut support.


useSpotlight

A hook that binds a keyboard shortcut and returns open/close/toggle handlers. This is the building block used internally by SpotlightProvider.

const { isOpen, open, close, toggle } = useSpotlight(options?);

Options

| Option | Type | Default | Description | | ---------- | ------------ | --------- | ---------------------------------------------------------- | | shortcut | string | "mod+k" | Key combination (see Shortcut Format). | | onOpen | () => void | — | Fired when the state transitions to open. | | onClose | () => void | — | Fired when the state transitions to closed. |

Return value

| Key | Type | Description | | -------- | ------------ | -------------------------------------- | | isOpen | boolean | Whether the palette is currently open. | | open | () => void | Programmatically open the palette. | | close | () => void | Programmatically close the palette. | | toggle | () => void | Toggle between open and closed. |

Example

const { isOpen, open, close } = useSpotlight({
  shortcut: "mod+shift+p",
  onOpen: () => analytics.track("spotlight_opened"),
});

useSpotlightContext

Access the spotlight state and actions from anywhere inside a <SpotlightProvider> tree.

const { isOpen, open, close, toggle, setActions } = useSpotlightContext();

| Key | Type | Description | | ------------ | -------------------------------------- | ------------------------------------------- | | isOpen | boolean | Whether the palette is currently open. | | open | () => void | Open the palette. | | close | () => void | Close the palette. | | toggle | () => void | Toggle the palette. | | setActions | (actions: SpotlightAction[]) => void | Replace the current action list at runtime. |

Throws an error if called outside of <SpotlightProvider>.


SpotlightAction

The shape of each item in the palette.

| Field | Type | Required | Description | | ------------- | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | Yes | Unique identifier for the action. | | label | string | Yes | Primary text displayed in the list. | | onSelect | () => void | Yes | Called when the action is activated (click or Enter). | | description | string | No | Secondary line of text shown below the label. | | icon | React.ReactNode | No | Icon rendered to the left of the label (emoji or component). | | shortcut | string | No | Keyboard shortcut for this action (e.g. "⌘S", "mod+shift+s"). Displayed on the right of the row and registered as a real global keybinding. Fires onSelect when pressed, even when the palette is closed. Does not fire while the user is typing in an input or textarea. | | group | string | No | Category name used to visually group related actions. | | keywords | string[] | No | Additional search terms to improve fuzzy-match discoverability. |


Shortcut Format

Shortcuts are strings composed of modifier names joined by +. The last segment is the key character (case-insensitive).

| Token | Maps to | | ------- | --------------------------------------- | | mod | Cmd on macOS, Ctrl on Windows/Linux | | ctrl | Ctrl | | cmd | Cmd (macOS) | | shift | Shift | | alt | Alt / Option |

"mod+k"          →  Cmd/Ctrl + K          (default)
"mod+shift+p"    →  Cmd/Ctrl + Shift + P
"alt+space"      →  Alt + Space
"ctrl+shift+f"   →  Ctrl + Shift + F

Unicode symbols are also accepted on action-level shortcut fields:

"⌘K"    →  Cmd/Ctrl + K
"⌘⇧P"   →  Cmd/Ctrl + Shift + P
"⌃S"    →  Ctrl + S
"⌥T"    →  Alt + T

Action Shortcuts

Every action with a shortcut field has that shortcut automatically registered as a real global keyboard listener by <SpotlightProvider>. Pressing the shortcut calls the action's onSelect directly — the palette does not need to be open.

const actions = [
  {
    id: "save",
    label: "Save Document",
    shortcut: "⌘S", // rendered as a badge AND bound as ⌘/Ctrl+S
    onSelect: () => save(),
  },
  {
    id: "format",
    label: "Format Code",
    shortcut: "mod+shift+f", // text notation works too
    onSelect: () => format(),
  },
];

Behaviour notes:

  • Shortcuts are re-registered whenever the actions array changes.
  • The listener is skipped when an <input>, <textarea>, or contentEditable element has focus, so typing in the search box (or any form field) will never trigger an action shortcut.
  • / mod resolve to Cmd on macOS and Ctrl on Windows / Linux.

Keyboard Controls

| Key | Action | | ------------- | -------------------------------- | | | Move selection down. | | | Move selection up. | | Enter | Activate the highlighted action. | | Escape | Close the palette. | | Click outside | Close the palette. |


Theming

Pass theme to <SpotlightProvider> or <Spotlight>:

// Always use light theme
<SpotlightProvider theme="light" actions={actions}>…</SpotlightProvider>

// Always use dark theme
<SpotlightProvider theme="dark" actions={actions}>…</SpotlightProvider>

// Follow system preference or class-based theme (default)
<SpotlightProvider theme="auto" actions={actions}>…</SpotlightProvider>

"auto" mode

When theme="auto", the component resolves the active theme in this priority order:

  1. A dark or light class on <html> (set by Tailwind CSS, shadcn/ui, next-themes, etc.)
  2. The OS-level prefers-color-scheme media query.

Both sources are watched reactively — the palette updates instantly if the theme changes while it is open.


Usage Patterns

Actions with icons and groups

import { HomeIcon, SettingsIcon, UserIcon } from "lucide-react";

const actions = [
  {
    id: "home",
    label: "Home",
    icon: <HomeIcon size={16} />,
    group: "Navigation",
    onSelect: () => router.push("/"),
  },
  {
    id: "settings",
    label: "Settings",
    description: "Manage your account settings",
    icon: <SettingsIcon size={16} />,
    group: "Navigation",
    shortcut: "⌘,",
    onSelect: () => router.push("/settings"),
  },
  {
    id: "profile",
    label: "Profile",
    icon: <UserIcon size={16} />,
    group: "Navigation",
    shortcut: "⌘P",
    keywords: ["account", "avatar", "me"],
    onSelect: () => router.push("/profile"),
  },
];

Dynamically replacing actions

Use setActions to swap the action list at runtime — useful for context-sensitive palettes:

function Editor() {
  const { setActions, open } = useSpotlightContext();

  useEffect(() => {
    setActions([
      { id: "format", label: "Format Document", onSelect: formatDoc },
      { id: "save", label: "Save", shortcut: "⌘S", onSelect: save },
      { id: "close", label: "Close File", onSelect: closeFile },
    ]);
  }, []);

  return <button onClick={open}>Editor Commands</button>;
}

Custom open shortcut

<SpotlightProvider actions={actions} shortcut="mod+shift+p">
  <App />
</SpotlightProvider>

Responding to open/close events

<SpotlightProvider
  actions={actions}
  onOpen={() => analytics.track("palette_opened")}
  onClose={() => analytics.track("palette_closed")}
>
  <App />
</SpotlightProvider>

TypeScript

All types are exported from the package:

import type {
  SpotlightAction,
  SpotlightProps,
  UseSpotlightOptions,
  UseSpotlightReturn,
} from "@nobertdev/react-spotlight-search";

SpotlightAction

interface SpotlightAction {
  id: string;
  label: string;
  description?: string;
  icon?: React.ReactNode;
  shortcut?: string;
  group?: string;
  keywords?: string[];
  onSelect: () => void;
}

UseSpotlightOptions

interface UseSpotlightOptions {
  shortcut?: string;
  onOpen?: () => void;
  onClose?: () => void;
}

UseSpotlightReturn

interface UseSpotlightReturn {
  isOpen: boolean;
  open: () => void;
  close: () => void;
  toggle: () => void;
}

License

MIT © Nobert Langat


Support

If this package saves you time, consider buying me a coffee — it helps keep the work going!

Buy Me a Coffee