mrkeyoor.com_
Sat 08 Aug 22:51 UTC
npmUtilsupdated 08 Aug 2026

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.

Verdict

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.

API stability3/5Version 0.5 has a small, typed API centered on `Fzf`, `AsyncFzf`, `find`, selectors, options, and flat result entries. The migration guide is transparent about earlier 0.x changes: `maxResultItems` became `limit`, cache was removed, normalization defaults changed, match positions became a Set, and result fields were flattened. The current shape is coherent, but a package still below 1.0 offers less compatibility assurance.
Docs4/5The hosted docs cover strings, object selectors, combined fields, casing, highlight positions, tie-breakers, sorting, reverse matching, async cancellation, TypeScript, extended mode, and every result field. A migration guide gives concrete diffs. The main gaps are operational: performance advice is qualitative, async scheduling is not contrasted clearly with Web Workers, and the site is a client-rendered app rather than an easy-to-link per-option reference.
Maintenance2/5The repository is not archived, has only four open issues when pull requests are excluded, and was pushed in April 2025. Still, the latest npm release is 0.5.2 from April 2023 and there are no newer GitHub releases. With no dependencies, the package has little routine upgrade pressure, but users should expect a stable snapshot rather than frequent fixes or a visible path to a 1.0 contract.
Ecosystem4/5The package recorded 3,332,191 downloads in the measured week, has 954 GitHub stars, works in browsers and Node, ships both module formats plus declarations, and has no runtime dependencies. It benefits from a familiar ranking model and query syntax derived from the original fzf. It is an algorithm building block rather than a UI ecosystem, so accessibility, framework components, virtualization, and state integration come from your own code.

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
Skip it if

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

PackageRegistryPick it when
fuse.jsnpmYou need weighted multi-field object search, threshold tuning, and typo-oriented fuzzy matching
fuzzysortnpmRaw browser matching speed and prepared targets matter more than matching the fzf ranking model
fast-fuzzynpmYou want a compact general fuzzy search API with configurable similarity behavior
match-sorternpmYou prefer deterministic human-friendly ranking tiers for filtering UI lists