Skip to content

Fuzzy Select

Filterable select input with fuzzy matching for Ink 5. Type to filter, arrow keys to navigate, enter to select.

All existing fuzzy-select packages for Ink target Ink 0.x or 2.x and are no longer maintained. This package is built for Ink 5 with React 18.

Install

Terminal window
npm install @matthesketh/ink-fuzzy-select

Usage

import React from "react";
import { render } from "ink";
import { FuzzySelect } from "@matthesketh/ink-fuzzy-select";
const items = [
{ label: "JavaScript", value: "js" },
{ label: "TypeScript", value: "ts" },
{ label: "Python", value: "py" },
{ label: "Rust", value: "rs" },
{ label: "Go", value: "go" },
];
function App() {
return (
<FuzzySelect
items={items}
onSelect={(item) => {
console.log("Selected:", item.value);
process.exit(0);
}}
onCancel={() => {
console.log("Cancelled");
process.exit(0);
}}
placeholder="Search languages..."
maxVisible={5}
/>
);
}
render(<App />);

Props

PropTypeDefaultDescription
itemsFuzzySelectItem[](required)Array of items to select from
onSelect(item: FuzzySelectItem) => void(required)Called when an item is selected with Enter
onCancel() => voidundefinedCalled when Escape is pressed
placeholderstring"Type to filter..."Shown in the input when the query is empty
maxVisiblenumber10Maximum number of items visible in the list
renderItem(item, selected, highlighted) => ReactNodeundefinedCustom item renderer

FuzzySelectItem

interface FuzzySelectItem {
label: string;
value: string;
}

Keyboard

KeyAction
Any characterAppends to the filter query
BackspaceRemoves the last character from the query
Up / DownNavigate the filtered list
EnterSelect the highlighted item
EscapeCancel (calls onCancel)

Fuzzy matching

The built-in fuzzy matcher checks whether all query characters appear in order in the label (case-insensitive). Results are scored by:

  • Consecutive matches — +2 points for each character that immediately follows the previous match
  • Start-of-string — +3 points if the first query character matches the first character of the label

Higher-scoring matches appear first in the filtered list.

You can also import the matcher directly:

import { fuzzyMatch } from "@matthesketh/ink-fuzzy-select";
const result = fuzzyMatch("ts", "TypeScript");
// { matches: true, score: 3, indices: [0, 5] }

Custom rendering

Use renderItem to control how each item is displayed. The highlighted parameter is a string with matched characters wrapped in brackets (e.g. "[T]ype[S]cript").

<FuzzySelect
items={items}
onSelect={handleSelect}
renderItem={(item, selected, highlighted) => (
<Text color={selected ? "green" : "white"}>
{selected ? "> " : " "}
{highlighted} ({item.value})
</Text>
)}
/>

Requirements

  • Ink >= 5.0.0
  • React >= 18.0.0