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

reef-search

v0.3.3

Published

Fuzzy, keyboard-first search for static sites. No backend, no build step, no account. Works across web pages, and indexes links as well for navigation.

Readme

reef_transparent

Reef Search

Notice: This project is under active development. Expect new versions daily, and expect breaking changes between them.

Reef Search is a zero-build, single-script-tag search overlay for static sites. Paste in one <script> tag and visitors get a fast, keyboard-first search modal (Cmd/Ctrl+K) backed by an in-browser index — no server, no build step, no account.

It crawls your sitemap, extracts page content in the browser, and can optionally surface more than just page text: buttons, form fields, downloadable files, media captions, and structured FAQ data on the pages it indexes.

Contents

Features

  • Single script-tag install — no build step, no backend, no framework assumptions.
  • In-browser indexing — pages are fetched, parsed with DOMParser, and indexed entirely client-side; nothing runs from fetched HTML.
  • Universal indexing model — beyond page sections, the indexer can also surface:
    • Actions — buttons, [role="button"] elements, and <summary> toggles.
    • Fields — form inputs, textareas, and selects (indexed for focus, never auto-filled).
    • Links — external links, tracked as their own result type.
    • Files — downloadable links (PDF, Office docs, spreadsheets, archives, CSV).
    • Media — images with alt text/captions, and video/audio with a title or caption track.
    • Structured dataapplication/ld+json FAQ pages and other typed metadata, shown as inline answers.
  • Accessible, themeable modal — rendered in a Shadow DOM root so host-page styles never leak in or out.
  • Typo-tolerant search — falls back to a Levenshtein-based "did you mean" suggestion when there are no direct matches.
  • Developer API — customize hotkeys, register selection callbacks, and control the modal programmatically.

How it works

  1. The page loads the script with defer.
  2. On boot, Reef looks for sitemap.xml (or your configured path) relative to the current page.
  3. If a sitemap is found, it fetches the linked pages in parallel (concurrency-limited) and extracts sections, actions, fields, links, files, media, and structured data from each.
  4. If no sitemap resolves, Reef falls back to indexing just the current page.
  5. Everything is held in an in-memory index — no query ever leaves the browser.
  6. Pressing Cmd/Ctrl+K opens the modal; typing filters the in-memory index and ranks results by field weight (heading matches score highest, body-word matches lower).
  7. Selecting a result navigates to it, focuses a field, or runs a safe same-page action, depending on its type.

Install

<script src="reef.min.js"></script>

Press Cmd/Ctrl+K on the page to open the overlay.

Configuration

Set these as attributes on the script tag (or data-* equivalents where noted).

| Attribute | Default | Purpose | |---|---|---| | data-sitemap | /sitemap.xml | Sitemap path override | | data-max-pages | 500 | Maximum number of sitemap pages to fetch | | data-scope | — | CSS selector limiting extraction to a specific content root | | data-index-actions | true | Enable indexing of buttons/toggles (also gates field indexing) | | data-index-media | true | Enable indexing of images, video, and audio | | data-index-structured-data | true | Enable indexing of JSON-LD structured data | | data-actions-mode | execute | execute or navigate-only — see Safety | | data-hotkey | ctrlk,cmdk | Custom keyboard shortcut(s) | | data-placeholder | Search this site | Custom input placeholder text | | data-primary-color | #43d9c8 | Primary accent color | | data-background-color | rgba(20,30,28,0.65) | Modal background color | | data-text-color | #edebe6 | Text color | | data-border-color | rgba(67,217,200,0.25) | Border color | | data-radius | 16 | Border radius in pixels | | data-mode | regular | regular, opaque, or high-contrast | | data-headless | false | When true, builds index without rendering the modal | | data-on-ready | — | (Runtime API use) Callback fired when index is ready |

Result types

| Type | Label | Behavior on select | |---|---|---| | section | Section | Navigates to the heading anchor | | action | Action | Clicks the control if it's safe and on the current page, otherwise navigates first | | field | Field | Focuses (and selects) the matching input | | link | Link | Navigates to the external link | | file | File | Navigates to the downloadable resource | | media | Media | Navigates to the page containing the image/video/audio | | structured | Answer | Shows an inline preview and navigates to the source page |

Runtime API

  • window.Reef.open() — opens the modal (no-op in headless mode).
  • window.Reef.close() — closes the modal (no-op in headless mode).

When in headless mode, use window.Reef.search(query) and window.Reef.getIndex() to query and access indexed data.

Developer API

Headless Mode

For developers who want to build their own UI or integrate Reef into custom projects, use headless mode to get the indexed data without the default modal:

// Initialize in headless mode
window.Reef = new ReefSearch({ headless: true });

// Or via script attribute
<script src="reef.min.js" data-headless="true"></script>
// Get all indexed records (available after indexing completes)
const allRecords = window.Reef.getIndex();

// Search the index programmatically
const results = window.Reef.search('installation', 10);

// Set a callback that fires when the index is ready
window.Reef.setOnReady(({ index }) => {
  console.log('Index ready with', index.length, 'records');
});

// Get sitemap URLs without building the index
window.Reef.getSitemapUrls().then(urls => {
  console.log('Found URLs:', urls);
});

// Rebuild the index (useful after adding custom records)
window.Reef.reindex();

Hotkey Management

// Get current hotkey
const current = window.Reef.getHotkey(); // Returns "ctrlk,cmdk" by default

// Set custom hotkey
window.Reef.setHotkey('altk,f'); // Opens with Alt+K or Ctrl+F
window.Reef.setHotkey('ctrlshiftk'); // Opens with Ctrl+Shift+K

Supported hotkey keys: ctrlk, cmdk, ctrlshiftk, altk, f

Selection Callbacks

// Register callback for selected results
window.Reef.onselect(function(result) {
  console.log('Selected:', result.type, result.headingText);
});

// Remove callback
window.Reef.offselect();

The callback receives an IndexRecord object with properties:

  • type — Result type (section, action, field, link, file, media, structured)
  • headingText — The title/label of the result
  • url — Target URL
  • breadcrumb — Page context
  • bodyText — Full text content
  • destructive — Whether action is destructive (actions only)
  • selector — CSS selector for the element (actions/fields only)

Programmatic Control

// Open with a pre-filled query
window.Reef.openWithQuery('installation');

// Check if modal is open
if (window.Reef.isOpenState()) {
  console.log('Search is open');
}

Index Manipulation

// Get all indexed records
const allRecords = window.Reef.getIndex();

// Search the index programmatically (limit optional, default 8)
const results = window.Reef.search('query', 10);

// Add custom records
window.Reef.addCustomRecords([{
  id: 'custom-1',
  url: window.location.href,
  headingText: 'Custom Result',
  headingId: 'custom-1',
  breadcrumb: '',
  bodyText: 'Custom searchable content',
  type: 'section'
}]);

// Rebuild index (re-crawls sitemap)
window.Reef.reindex();

// Rebuild index and wait for it to complete
window.Reef.rebuildIndex().then(() => {
  console.log('Index rebuilt');
});

Headless Control

// Switch to headless mode (removes modal and hotkey)
window.Reef.setHeadless(true);

// Switch back to regular mode (requires re-initialization)
window.Reef.setHeadless(false);

Runtime Styling

// Update color scheme
window.Reef.setColorScheme({
  primary: '#ff6b6b',
  secondary: '#4ecdc4',
  background: 'rgba(255,255,255,0.8)',
  text: '#111111',
  border: '#cccccc',
  radius: 12
});

// Change mode
window.Reef.setMode('high-contrast'); // or 'opaque', 'regular'

// Set font family
window.Reef.setFontFamily('system-ui, sans-serif');

// Update placeholder
window.Reef.setPlaceholder('Search docs...');

Configuration Inspection

// Get current configuration
const config = window.Reef.getConfig();
console.log(config.hotkey, config.mode);

Custom Modal Examples

Minimal Custom Modal

Build your own search interface using the headless API:

<script src="reef.min.js" data-headless="true"></script>

<div id="custom-search" style="display:none;">
  <input type="text" id="search-input" placeholder="Search..." />
  <ul id="search-results"></ul>
</div>
const searchModal = document.getElementById('custom-search');
const searchInput = document.getElementById('search-input');
const searchResults = document.getElementById('search-results');

window.Reef.setOnReady(() => {
  searchModal.style.display = 'block';
});

searchInput.addEventListener('input', (e) => {
  const query = e.target.value;
  const results = window.Reef.search(query, 10);
  
  searchResults.innerHTML = results.map(r => `
    <li>
      <strong>${r.headingText}</strong>
      <small>${r.breadcrumb || r.url}</small>
    </li>
  `).join('');
});

// Open your custom modal
document.getElementById('open-search').addEventListener('click', () => {
  searchModal.style.display = 'block';
  searchInput.focus();
});

React/Vue Component Integration

// React example
import { useEffect, useState } from 'react';

function SearchComponent() {
  const [index, setIndex] = useState([]);
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  useEffect(() => {
    // Wait for Reef to be ready
    if (window.Reef) {
      window.Reef.setOnReady(({ index }) => {
        setIndex(index);
      });
    }
  }, []);

  const handleSearch = (e) => {
    const q = e.target.value;
    setQuery(q);
    setResults(window.Reef?.search(q, 10) || []);
  };

  return (
    <div>
      <input value={query} onChange={handleSearch} placeholder="Search..." />
      <ul>
        {results.map(r => (
          <li key={r.id} onClick={() => window.location.href = r.url}>
            {r.headingText}
          </li>
        ))}
      </ul>
    </div>
  );
}

Custom Styled Dropdown Search

// Create a dropdown search in your navbar
const createDropdownSearch = () => {
  const container = document.createElement('div');
  container.innerHTML = `
    <div class="dropdown-search">
      <button id="search-trigger">Search</button>
      <div class="dropdown-menu" style="display:none;">
        <input type="text" placeholder="Type to search..." />
        <div class="results"></div>
      </div>
    </div>
  `;
  document.querySelector('nav').appendChild(container);

  window.Reef.setOnReady(({ index }) => {
    const input = container.querySelector('input');
    const resultsDiv = container.querySelector('.results');
    
    input.addEventListener('input', (e) => {
      const results = window.Reef.search(e.target.value, 5);
      resultsDiv.innerHTML = results.map(r => `
        <div class="result" data-url="${r.url}">
          ${r.headingText}
        </div>
      `).join('');
    });
  });
};

createDropdownSearch();

Build-Time Index Generation

Use Reef in headless mode during static site generation to pre-compute your index:

// In your build script
import { extractSections, createSearchIndex, addToIndex } from 'reef-search';
import { readFileSync, readdirSync } from 'fs';

async function generateIndex() {
  const index = createSearchIndex();
  const files = readdirSync('./docs').filter(f => f.endsWith('.html'));
  
  for (const file of files) {
    const html = readFileSync(`./docs/${file}`, 'utf-8');
    const sections = extractSections(html, `/docs/${file}`);
    addToIndex(index, sections);
  }
  
  // Write index to JSON file
  require('fs').writeFileSync(
    './dist/search-index.json', 
    JSON.stringify(index.allSections)
  );
}

Custom Result Renderer

const renderResults = (results) => {
  return `
    <div class="custom-results">
      ${results.map(r => `
        <div class="result-item" data-type="${r.type}">
          <span class="type-badge">${r.type}</span>
          <span class="title">${r.headingText}</span>
          <span class="snippet">${r.bodyText.substring(0, 80)}...</span>
          ${r.destructive ? '<span class="warning">⚠️</span>' : ''}
        </div>
      `).join('')}
    </div>
  `;
};

// Use with headless mode
window.Reef.setOnReady(() => {
  document.getElementById('results').innerHTML = renderResults(window.Reef.getIndex());
});

Keyboard shortcuts

  • Cmd/Ctrl+K — open the modal (configurable via data-hotkey).
  • ↑ / ↓ — move between results.
  • Enter — run or navigate to the selected result.
  • Escape — close the modal.

Safety and execution model

Because selecting a search result can trigger behavior on the page, execution is deliberately conservative:

  • Forms are never auto-filled or auto-submitted — fields only receive focus.
  • Labels matching destructive verbs (delete, remove, pay, checkout, confirm, etc.) are flagged as destructive.
  • Actions on the current page are dispatched as a real click event against the matched element; if the element can't be found, the user sees a toast instead of a silent failure.
  • Actions that target a different page are deferred: Reef stores the pending action and navigates there first, resolving it the next time the overlay initializes on that page rather than mutating anything before the destination has loaded.
  • With the default data-actions-mode="execute", a destructive action on the current page will still run when selected. Set data-actions-mode="navigate-only" if you want destructive actions to only scroll to and highlight the element instead of triggering it automatically.

Current limitations

  • No persistent cache yet. The index is rebuilt from the sitemap on every full page load; there is no IndexedDB (or other) persistence across sessions or navigations yet.
  • No worker offload yet. Indexing and search both run on the main thread.
  • No same-origin crawl fallback. If no sitemap resolves, Reef indexes only the current page rather than crawling outward from it.
  • No OCR for images, and no indexing of client-rendered content that only appears after hydration on other pages (fetched HTML doesn't execute scripts).
  • Several data-* attributes described above are accepted but not yet wired up (see Configuration).

Roadmap

  • Web Worker offload for indexing and querying.
  • IndexedDB-backed persistent cache with version/TTL invalidation.
  • Same-origin breadth-first fallback crawl when no sitemap is present.
  • Wiring up data-index-hidden, data-file-extensions, and data-exclude-action.
  • Deciding whether answer-type results should outrank section matches on exact queries.

Development

Install dependencies and build:

npm install
npm run build

The generated bundle is written to reef.min.js.

Run the test suite:

npm test