fzf
`fzf` is a dependency-free JavaScript port of the matching and ranking algorithm behind the Go command-line fuzzy finder. Give it a fixed list of strings or objects plus a selector, then call `find()` to receive ranked items, scores, match boundaries, and exact character positions for highlighting. It is useful inside command palettes and client-side search boxes, but it supplies no input, dropdown, keyboard controls, virtualization, or command-line interface of its own.
This is a good small engine for a hand-built command palette when fzf-style subsequence ranking and highlight positions are the point. Choose a search library with fields and typo thresholds for richer discovery, and do not mistake `AsyncFzf` for background-thread execution.
Use it if
- You are building a browser command palette and want rankings similar to the original fzf algorithm
- You need match positions to highlight scattered query characters in each result
- You search a stable in-memory list of strings or objects and can construct a new finder when the list changes
- You want smart-case, diacritic normalization, custom tie-breaking, extended query syntax, and no runtime dependencies
- You want the interactive terminal program: this npm package only ports the matching algorithm, and its own documentation recommends the original Go fzf for command-line use
- Your dataset changes in place: the constructor precomputes normalized rune arrays, and the docs explicitly say to reinitialize after modifying the input list
- You need typo distance, weighted fields, indexed documents, or tokenized full-text search: this is ordered character fuzzy matching over one selector string
- Your large search must leave the main thread entirely: `AsyncFzf` processes chunks with timers and cancels earlier searches, but it does not create a Web Worker
- You need a fast-moving, post-1.0 API commitment: the current package is 0.5.2 from April 2023, its last repository push was April 2025, and earlier 0.x migrations renamed options and changed result shapes
Setup reality
Install `fzf`; version 0.5.2 has no runtime dependencies, ships TypeScript declarations, and exposes ESM and CommonJS entry points. The measured bundle is 5.7 KB gzipped. There is no CSS, DOM component, or framework peer, because the package only computes matches. You must build the input, focus management, arrow-key navigation, selection, accessibility roles, result virtualization, and highlighted rendering yourself. Construction is real work: it calls your selector for every item and converts every selected string into normalized code points. Keep the `Fzf` instance memoized for a stable list, but create a new instance whenever items or selector-visible fields change; mutating the original array leaves its cached search representation stale. Synchronous `find()` scans the list on the calling thread. The v2 algorithm gives better positions but costs more than v1, so the docs suggest v1 for short early queries when typing lags. `AsyncFzf` yields after chunks of 1,000 items, which improves responsiveness but still performs the matching on the same JavaScript thread. Each new async search cancels the prior one by rejecting it with the string `search cancelled`; catch that rejection so rapid typing does not create unhandled promises. The default query mode is basic fuzzy matching. Extended operators such as prefix, suffix, exact, inverse, and OR only work when you import and select `extendedMatch` or `asyncExtendedMatch`. Results for an empty query return the leading input items, limited by `limit`, with score zero and empty positions. Finally, normalization is on and casing is smart by default, so highlighting should use the same normalized selector text that was searched.
Patterns
Rank strings with a basic fuzzy querysearch-string-list
import { Fzf } from 'fzf';
const languages = ['go', 'javascript', 'python', 'rust', 'kotlin', 'elixir', 'lisp'];
const fzf = new Fzf(languages);
const matches = fzf.find('li');
console.log(matches.map(({ item }) => item)); // ['lisp', 'kotlin', 'elixir']Keep the finder instance between keystrokes. Reconstructing it repeats normalization work for the entire list.
Search objects through a selectorsearch-object-list
import { Fzf } from 'fzf';
const commands = [
{ id: 'open', label: 'Open file' },
{ id: 'close', label: 'Close editor' },
];
const fzf = new Fzf(commands, {
selector: (command) => command.label,
});
const command = fzf.find('opf')[0]?.item;A selector is required by the TypeScript types for non-string items, and results retain the original object in `item`.
Combine fields into one searchable targetsearch-multiple-fields
const fzf = new Fzf(users, {
selector: (user) => `${user.name} ${user.email} ${user.team}`,
limit: 20,
});
const results = fzf.find('ada platform');Combined fields are one plain string. There is no per-field weight or structured query support.
Choose case and normalization behaviorcontrol-case-matching
const insensitive = new Fzf(names, {
casing: 'case-insensitive',
normalize: true,
});
const literal = new Fzf(names, {
casing: 'case-sensitive',
normalize: false,
});Defaults are `smart-case` and normalization enabled. Under smart case, uppercase query characters make matching case-sensitive.
Render matched character positionshighlight-match-positions
function Highlight({ text, positions }) {
return Array.from(text.normalize()).map((char, index) =>
positions.has(index)
? <mark key={index}>{char}</mark>
: <span key={index}>{char}</span>
);
}
const entry = fzf.find(query)[0];
const node = entry && <Highlight text={entry.item} positions={entry.positions} />;`positions` is a Set of character indices. Rendering text as React children avoids creating unsafe HTML from list content.
Cap work returned to the UIlimit-result-count
const fzf = new Fzf(items, { limit: 32 });
const visible = query ? fzf.find(query) : [];The default limit is Infinity. A finite limit reduces rendering work, but the matcher still examines the input list.
Prefer shorter results when scores tiebreak-score-ties
import { Fzf, byLengthAsc, byStartAsc } from 'fzf';
const fzf = new Fzf(items, {
tiebreakers: [byStartAsc, byLengthAsc],
});Tie-breakers run left to right only when scores are equal, and they are ignored entirely when `sort` is false.
Filter without rankingpreserve-input-order
const fzf = new Fzf(recentCommands, {
sort: false,
limit: 20,
});
const matchesInRecentOrder = fzf.find(query);With sorting disabled, matched items remain in original input order and configured tie-breakers have no effect.
Match file paths from the endprefer-filename-match
const files = [
'src/components/composite-input.ts',
'src/components/portal.ts',
];
const fzf = new Fzf(files, { forward: false });
const matches = fzf.find('comp');Backward matching changes which occurrence is preferred and highlighted; it does not reverse the input string.
Enable original fzf-style query operatorsuse-extended-query
import { Fzf, extendedMatch } from 'fzf';
const fzf = new Fzf(files, { match: extendedMatch });
const exactJsTests = fzf.find("^src 'test !fixture .js$");Prefix, exact, inverse, suffix, and OR operators require `extendedMatch`; the default `basicMatch` treats the query as a fuzzy term.
Use the faster v1 matcher for short queriesswitch-fuzzy-algorithm
const accurate = new Fzf(items);
const fast = new Fzf(items, { fuzzy: 'v1' });
function search(query) {
return query.length <= 3 ? fast.find(query) : accurate.find(query);
}The documentation suggests this split when early keystrokes lag; v2 remains the default because it produces better match positions.
Handle cancellation during rapid async searchessearch-asynchronously
import { AsyncFzf } from 'fzf';
const fzf = new AsyncFzf(items, { limit: 50 });
async function search(query) {
try {
return await fzf.find(query);
} catch (error) {
if (error === 'search cancelled') return [];
throw error;
}
}A new call cancels the prior search by rejecting with a string. Work is chunked on the current thread, not moved into a worker.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| fuse.js | npm | You need weighted multi-field object search, threshold tuning, and typo-oriented fuzzy matching |
| fuzzysort | npm | Raw browser matching speed and prepared targets matter more than matching the fzf ranking model |
| fast-fuzzy | npm | You want a compact general fuzzy search API with configurable similarity behavior |
| match-sorter | npm | You prefer deterministic human-friendly ranking tiers for filtering UI lists |