mrkeyoor.com_
Wed 23 Sept 00:34 UTC
npmUtilsupdated 22 Sept 2026

fzf review

fzf is a JavaScript implementation of the original terminal finder's matching and ranking algorithm. Construct it with strings or objects plus a selector, then find() returns ordered entries with scores, match ranges, and a Set of character positions for highlighting. It can run basic or extended query syntax and has a timer-chunked AsyncFzf class. It does not provide a terminal program, search input, dropdown, keyboard behavior, or list virtualization. Version 0.5.2 only fixes declaration discovery for TypeScript projects using node16, nodenext, or bundler module resolution. Our install confirmed that both require() and ESM import work.

Verdict

fzf 0.5.2 installed in 0.5 seconds with 0 dependencies and measured 6.1 KB gzipped in our browser build, making it a sensible matcher for a custom command palette. Pick Fuse.js for weighted fields or a Worker-based design when large searches cannot run on the UI thread.

We installed it

Lab card: what happened when we installed fzfScreenshot of fzf documentation
Install✓ · 0.5s1 package on disk · 1 MB
ImportESM import works · require() works · ESM package with exports map
Browser6.1 KBgzipped (15.1 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does fzf install cleanly?

Yes. In a fresh container with an empty cache, npm install fzf finished in 0.5s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does fzf add to a browser bundle?

6.1 KB gzipped (15.1 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does fzf work with both ESM and CommonJS?

Yes. Both import 'fzf' and require('fzf') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does fzf include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

fzf or fuse.js: which should you use?

fuse.js: Use it for weighted object keys, thresholds, and edit-distance-oriented fuzzy search. fzf 0.5.2 installed in 0.5 seconds with 0 dependencies and measured 6.1 KB gzipped in our browser build, making it a sensible matcher for a custom command palette.

When should you not use fzf?

You want the interactive fzf terminal application; this npm package ports its matcher for JavaScript and has no CLI

API stability3/5Version 0.5.2 exposes a compact typed surface: Fzf, AsyncFzf, find(), selectors, matching functions, tie-breakers, and flat result entries. The migration notes show material changes before 0.5: maxResultItems became limit, cache disappeared, normalization and direction defaults changed, positions became a Set, and nested result fields were flattened. Current calls are coherent, though a 0.x release line can still make another minor version breaking.
Docs4/5The hosted documentation explains basic and extended queries, object selectors, combined fields, casing, Unicode normalization, position highlighting, result limits, sorting, reverse matching, tie-breakers, both fuzzy algorithms, AsyncFzf cancellation, and TypeScript. A migration page lists exact 0.4 to 0.5 differences. It is less clear that timer chunks stay on one thread, and it gives qualitative rather than measured guidance for choosing v1 or AsyncFzf.
Maintenance2/5npm released 0.5.2 on April 25, 2023 to repair type visibility under node16, nodenext, and bundler resolution. GitHub recorded its last push on April 14, 2025 and currently shows 4 open issues plus 4 pull requests; the repository is not archived. With 0 runtime dependencies, dependency churn is irrelevant, yet more than 3 years without a release leaves open CommonJS and multi-property search requests unresolved in the published package.
Ecosystem4/5npm reported 3,456,454 downloads in the week ending August 24, 2026, and GitHub lists 954 stars. The BSD-3-Clause package works in browsers and Node, ships declarations, has explicit import and require entries, and borrows familiar query ideas from the Go fzf project. It remains a matching primitive rather than a component ecosystem: every framework binding, accessible listbox, virtual list, search Worker, and application action model is separate.

Use it if

  • A browser command palette needs fzf-style subsequence ranking and exact highlight positions
  • The searchable array is stable and can be preprocessed once per data change
  • Smart-case matching, diacritic normalization, and custom score tie-breakers fit the product
  • The team wants an algorithm package and plans to build the accessible interface itself
Skip it if

Setup reality

Our clean install of fzf 0.5.2 took 0.5 seconds on Node 22 and left 1 package using 1 MB. npm audit returned 0 known vulnerabilities. The package is 132 KB unpacked, declares 0 direct dependencies and 0 peers, and includes TypeScript types. It is marked as ESM and has an exports map, while require() and ESM import both passed. Our complete browser import measured 15.1 KB minified and 6.1 KB gzipped.

No credentials, styles, DOM nodes, or config files come with it. Fzf preprocesses the full list when constructed by running the selector and normalizing characters. Memoize that instance while the array is unchanged. If an item label or selector-visible field changes, construct another finder; mutating the source array does not refresh the internal records. The selector combines object fields into one string, so it cannot give a title twice the weight of an email or return field-specific match positions.

find() scans on the caller's thread, and the default v2 matcher trades more computation for better highlight locations. The docs suggest v1 for short early queries if typing stalls. AsyncFzf divides work into chunks of 1,000 items and schedules those chunks with timers. A newer query cancels the earlier promise by rejecting it with the string search cancelled. Catch that exact value to prevent unhandled rejections, but use a Worker of your own when matching must leave the main thread.

Basic fuzzy matching is the default. Operators for prefix, suffix, exact terms, inverse terms, and OR require extendedMatch or asyncExtendedMatch. An empty query returns leading input items with score 0 and no positions, bounded only when limit is finite. Normalization is enabled and casing is smart by default, so an uppercase query changes case behavior. Version 0.5.2 fixes types under 3 TypeScript module-resolution modes; it does not change ranking or cancellation semantics.

Patterns

Rank a list of strings search-strings

import { Fzf } from 'fzf'

const languages = ['go', 'javascript', 'python', 'rust', 'kotlin', 'elixir', 'lisp']
const finder = new Fzf(languages)
const matches = finder.find('li')
console.log(matches.map(({ item }) => item))

Construct the 0.5.2 finder once for a stable array. Rebuilding it for every keystroke repeats normalization of every item.

Select the searchable object label search-objects

const commands = [
  { id: 'open', label: 'Open file' },
  { id: 'close', label: 'Close editor' },
]
const finder = new Fzf(commands, {
  selector: (command) => command.label,
})
const command = finder.find('opf')[0]?.item

TypeScript requires a selector for non-string items. Each result keeps the original object in its item property.

Search several fields as one target combine-fields

const finder = new Fzf(users, {
  selector: (user) => `${user.name} ${user.email} ${user.team}`,
  limit: 20,
})

const results = finder.find('ada platform')

The concatenation is one string with one score. fzf 0.5.2 cannot weight name above email or report which field matched.

Choose case and accent handling set-case-mode

const forgiving = new Fzf(names, {
  casing: 'case-insensitive',
  normalize: true,
})
const literal = new Fzf(names, {
  casing: 'case-sensitive',
  normalize: false,
})

Version 0.5 defaults to smart-case plus normalization. An uppercase query switches smart-case matching to case-sensitive.

Render matching characters safely highlight-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 = finder.find(query)[0]
const node = entry && <Highlight text={entry.item} positions={entry.positions} />

positions is a Set in 0.5.x. React children keep list text out of an HTML injection path.

Bound the result array limit-results

const finder = new Fzf(items, { limit: 32 })
const visible = query ? finder.find(query) : []

The default limit is Infinity. Setting 32 reduces rendering work, though the matcher still considers the input collection.

Prefer early and short equal-score matches break-ties

import { Fzf, byLengthAsc, byStartAsc } from 'fzf'

const finder = new Fzf(items, {
  tiebreakers: [byStartAsc, byLengthAsc],
})

Tie-breakers are evaluated from left to right only after equal scores. With sort false, they are not used.

Filter while keeping source order preserve-order

const finder = new Fzf(recentCommands, {
  sort: false,
  limit: 20,
})
const matches = finder.find(query)

sort false leaves matches in their input order, which is useful when the array already expresses recency or priority.

Favor matches near a filename prefer-path-end

const files = [
  'src/components/composite-input.ts',
  'src/components/portal.ts',
]
const finder = new Fzf(files, { forward: false })
const matches = finder.find('comp')

forward false prefers a later matching occurrence. It does not reverse either the query or the target text.

Turn on fzf query operators use-extended-syntax

import { Fzf, extendedMatch } from 'fzf'

const finder = new Fzf(files, { match: extendedMatch })
const results = finder.find("^src 'test !fixture .js$")

Prefix, exact, inverse, suffix, and OR syntax only works with extendedMatch. basicMatch reads those characters as query content.

Use v1 for early keystrokes switch-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)
}

v2 is the default because it finds better highlight positions. The documentation suggests v1 when short queries cause visible input delay.

Catch a superseded async search handle-async-cancellation

import { AsyncFzf } from 'fzf'

const finder = new AsyncFzf(items, { limit: 50 })
async function search(query) {
  try {
    return await finder.find(query)
  } catch (error) {
    if (error === 'search cancelled') return []
    throw error
  }
}

A new call rejects the previous one with the string search cancelled. AsyncFzf uses 1,000-item timer chunks on the same thread.

Alternatives

PackageRegistryPick it when
fuse.jsnpmUse it for weighted object keys, thresholds, and edit-distance-oriented fuzzy search
fuzzysortnpmUse it when prepared targets and browser matching speed outweigh fidelity to fzf ranking
fast-fuzzynpmUse it for a smaller general similarity API with configurable scoring
match-sorternpmUse it when predictable ranking tiers fit a filtered UI better than fzf scoring

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.