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

@smart-input/typeahead

v1.0.5

Published

Typeahead autocomplete and lookup component for building smart input experiences

Readme

@smart-input/typeahead

A typeahead/autocomplete lookup component for the Open Input editor. Provides intelligent suggestions as users type, with customizable data sources and filtering.

Features

  • 🔍 Smart Suggestions - Show contextual suggestions as users type
  • ⌨️ Keyboard Navigation - Navigate suggestions with arrow keys and select with Enter
  • 🎯 Customizable Triggers - Define custom trigger characters (@, #, /, etc.)
  • 📊 Flexible Data Sources - Sync or async data fetching
  • 🎨 Custom Rendering - Full control over suggestion appearance
  • 🔄 Real-time Filtering - Client-side or server-side filtering
  • Accessible - ARIA-compliant with screen reader support

🚀 Live Demo

Try the interactive examples online: https://markgregg.github.io/smart-input/

Installation

npm install @smart-input/typeahead @smart-input/core zustand
# or
pnpm add @smart-input/typeahead @smart-input/core zustand
# or
yarn add @smart-input/typeahead @smart-input/core zustand

Quick Start

Basic Usage

import { SmartInput, Editor } from '@smart-input/core';
import { TypeaheadLookup } from '@smart-input/typeahead';
import '@smart-input/core/style.css';
import '@smart-input/typeahead/style.css';

const users = [
  { id: 1, name: 'John Doe', username: 'johndoe' },
  { id: 2, name: 'Jane Smith', username: 'janesmith' },
  { id: 3, name: 'Bob Wilson', username: 'bobwilson' }
];

function App() {
  return (
    <SmartInput>
      <TypeaheadLookup
        trigger="@"
        items={users}
        itemToString={(user) => user.name}
        onSelect={(user) => ({
          text: `@${user.username}`,
          style: { 
            backgroundColor: '#e3f2fd', 
            color: '#1976d2',
            padding: '2px 6px',
            borderRadius: '4px'
          }
        })}
      />
      <Editor placeholder="Type @ to mention someone..." />
    </SmartInput>
  );
}

Props

TypeaheadLookup

| Prop | Type | Required | Description | |------|------|----------|-------------| | trigger | string | Yes | Character that activates the lookup (e.g., '@', '#', '/') | | items | T[] | Yes* | Array of items to search (*or use fetchItems) | | itemToString | (item: T) => string | Yes | Convert item to display string | | onSelect | (item: T) => StyledBlockInfo | Yes | Convert selected item to styled block | | fetchItems | (query: string) => Promise<T[]> | No | Async function to fetch items | | filterItems | (items: T[], query: string) => T[] | No | Custom filtering function | | renderItem | (item: T) => ReactNode | No | Custom item renderer | | maxItems | number | No | Maximum suggestions to show (default: 10) | | minQueryLength | number | No | Minimum characters before showing suggestions (default: 0) | | debounceMs | number | No | Debounce delay for async fetching (default: 300) | | caseSensitive | boolean | No | Case-sensitive filtering (default: false) |

Examples

User Mentions (@)

<TypeaheadLookup
  trigger="@"
  items={users}
  itemToString={(user) => user.name}
  onSelect={(user) => ({
    text: `@${user.username}`,
    style: { 
      backgroundColor: '#e3f2fd',
      color: '#1976d2',
      fontWeight: 'bold'
    }
  })}
  renderItem={(user) => (
    <div className="user-mention">
      <img src={user.avatar} alt="" />
      <div>
        <div>{user.name}</div>
        <div className="username">@{user.username}</div>
      </div>
    </div>
  )}
/>

Hashtags (#)

const tags = ['javascript', 'react', 'typescript', 'nodejs', 'web'];

<TypeaheadLookup
  trigger="#"
  items={tags}
  itemToString={(tag) => tag}
  onSelect={(tag) => ({
    text: `#${tag}`,
    style: { 
      color: '#2196f3',
      fontWeight: '600'
    }
  })}
/>

Commands (/)

const commands = [
  { name: 'image', description: 'Insert an image', icon: '🖼️' },
  { name: 'code', description: 'Insert code block', icon: '💻' },
  { name: 'table', description: 'Insert a table', icon: '📊' }
];

<TypeaheadLookup
  trigger="/"
  items={commands}
  itemToString={(cmd) => cmd.name}
  onSelect={(cmd) => ({
    text: `/${cmd.name}`,
    style: { 
      backgroundColor: '#f5f5f5',
      fontFamily: 'monospace'
    }
  })}
  renderItem={(cmd) => (
    <div className="command-item">
      <span className="icon">{cmd.icon}</span>
      <div>
        <div className="name">{cmd.name}</div>
        <div className="description">{cmd.description}</div>
      </div>
    </div>
  )}
/>

Async Data Fetching

async function fetchUsers(query: string) {
  const response = await fetch(`/api/users?q=${query}`);
  return response.json();
}

<TypeaheadLookup
  trigger="@"
  fetchItems={fetchUsers}
  itemToString={(user) => user.name}
  onSelect={(user) => ({
    text: `@${user.username}`,
    style: { color: '#1976d2' }
  })}
  minQueryLength={2}
  debounceMs={500}
/>

Custom Filtering

function fuzzyFilter(items: User[], query: string) {
  const lowerQuery = query.toLowerCase();
  return items.filter(item => 
    item.name.toLowerCase().includes(lowerQuery) ||
    item.email.toLowerCase().includes(lowerQuery) ||
    item.username.toLowerCase().includes(lowerQuery)
  );
}

<TypeaheadLookup
  trigger="@"
  items={users}
  filterItems={fuzzyFilter}
  itemToString={(user) => user.name}
  onSelect={(user) => ({
    text: `@${user.username}`,
    style: { color: '#1976d2' }
  })}
/>

Multiple Typeaheads

<SmartInput>
  <TypeaheadLookup
    trigger="@"
    items={users}
    itemToString={(u) => u.name}
    onSelect={(u) => ({ text: `@${u.username}`, style: { color: 'blue' } })}
  />
  <TypeaheadLookup
    trigger="#"
    items={tags}
    itemToString={(t) => t}
    onSelect={(t) => ({ text: `#${t}`, style: { color: 'green' } })}
  />
  <TypeaheadLookup
    trigger="/"
    items={commands}
    itemToString={(c) => c.name}
    onSelect={(c) => ({ text: `/${c.name}`, style: { color: 'orange' } })}
  />
  <Editor placeholder="Type @, #, or / to trigger suggestions..." />
</SmartInput>

Keyboard Navigation

  • Arrow Down - Navigate to next suggestion
  • Arrow Up - Navigate to previous suggestion
  • Enter - Select highlighted suggestion
  • Escape - Close suggestions panel
  • Tab - Close suggestions and continue typing

Styling

Import the default styles:

import '@smart-input/typeahead/style.css';

Customize with CSS classes:

  • .typeahead-popup - Main popup container
  • .typeahead-list - Suggestions list
  • .typeahead-item - Individual suggestion
  • .typeahead-item--active - Highlighted suggestion

Example custom styles:

.typeahead-popup {
  max-height: 300px;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.typeahead-item {
  padding: 12px 16px;
  transition: background-color 0.2s;
}

.typeahead-item--active {
  background-color: #f0f7ff;
  color: #0066cc;
}

TypeScript Support

Full TypeScript definitions are included:

import type { 
  TypeaheadLookupProps,
  StyledBlockInfo,
  TypeaheadItem 
} from '@smart-input/typeahead';

interface User {
  id: number;
  name: string;
  username: string;
}

const props: TypeaheadLookupProps<User> = {
  trigger: '@',
  items: users,
  itemToString: (user) => user.name,
  onSelect: (user) => ({
    text: `@${user.username}`,
    style: { color: 'blue' }
  })
};

Accessibility

The typeahead component follows WCAG 2.1 AA guidelines:

  • ✅ ARIA labels and roles for screen readers
  • ✅ Keyboard-only navigation support
  • ✅ Focus management
  • ✅ Screen reader announcements for results
  • ✅ High contrast mode support

Advanced Usage

With React Components

Combine with @smart-input/reactblocks to render React components:

import { ReactBlocksManager } from '@smart-input/reactblocks';

function App() {
  const [reactBlocks, setReactBlocks] = useState([]);

  return (
    <SmartInput>
      <TypeaheadLookup
        trigger="@"
        items={users}
        itemToString={(u) => u.name}
        onSelect={(user) => {
          const blockId = `user-${user.id}-${Date.now()}`;
          
          setReactBlocks(prev => [...prev, {
            blockId,
            component: <UserCard user={user} />
          }]);

          return {
            id: blockId,
            text: `@${user.username}`,
            style: { color: 'transparent' }
          };
        }}
      />
      <ReactBlocksManager reactBlocks={reactBlocks} />
      <Editor />
    </SmartInput>
  );
}

Documentation

For more information, see:

Requirements

  • React 18.0.0 or higher
  • @smart-input/core 1.0.0 or higher
  • zustand 5.0.0 or higher

License

MIT © Mark Gregg