Text match TypeScript helper for React
v1.0.0Added in 1.0.0
Search that ignores case and diacritics, ranks the best matches first and returns the ranges to highlight.
Type in the field: zurich finds Zürich, sao paulo finds São Paulo, and every word of the query has to match somewhere. The best matches rise to the top and the matched letters are highlighted. Turn on fuzzy to let letters in order with gaps match too (dsdf finds Düsseldorf).
Text match is a free React helper in the React hooks and utilities category, written in TypeScript for React 19. It installs as source with npx wingo-ui@latest add text-match.
Add to a project
Requirements: React 19, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add text-matchUsage
Import filterByText from @/lib/text-match.
import { filterByText, splitByRanges } from "@/lib/text-match"
const cities = ["Zürich", "São Paulo", "Kraków", "Montréal", "Düsseldorf"]
export function CityResults({ query }: { query: string }) {
const results = filterByText(cities, query, (city) => city)
return (
<ul>
{results.map(({ item, ranges }) => (
<li key={item}>
{splitByRanges(item, ranges).map((part, i) =>
part.match ? <strong key={i}>{part.text}</strong> : <span key={i}>{part.text}</span>,
)}
</li>
))}
</ul>
)
}API
querydefault""stringThe demo's search text. Case and diacritics never matter; each word must match (in any order).
fuzzydefaultfalsebooleanMatchOptions.fuzzy: also accept the query's letters in order with gaps, scored lowest. Good for short lists of known names; leave it off for long lists, where it lets too much through.
filterByText<T>(items: readonly T[], query: string, getText: (item: T) => string | readonly string[], options?: { fuzzy?: boolean; limit?: number }) => { item: T; score: number; ranges: MatchRange[] }[]The matching items, best first and stable for ties. getText returns the label, or [label, ...keywords]: keywords (a country's calling code, an English name) can match too, a little lower, and ranges always refer to the label. An empty query returns every item in order.
Parts
matchText
functionOne text against a query, or null when a word is missing. Scores: exact 1, whole-text prefix 0.9, word prefix 0.75, inside a word 0.5, fuzzy 0.25, averaged over the query's words, with a small bonus for shorter texts. Signature: (text: string, query: string, options?: { fuzzy?: boolean }) => { score: number; ranges: MatchRange[] } | null.
splitByRanges
functionCuts a label into plain and matched pieces for rendering, e.g. matched pieces in font-semibold. Signature: (text: string, ranges: readonly MatchRange[]) => { text: string; match: boolean }[].
normalizeText
functionLowercase, without diacritics (é, ü, ñ, ø, the Romanian comma and cedilla s / t and friends), whitespace collapsed. Use it for keys and exact comparisons. Signature: (text: string) => string.
compareText
functionA cached Intl.Collator compare for sorting labels the way the locale's dictionary does, numbers in natural order. Signature: (a: string, b: string, locale?: string) => number.
Examples
People search with keywords
Email and team are keywords: "northwind" finds everyone with a northwind.example address. Type a name to see its match highlighted.
Sorting labels
compareText sorts accented names where a reader expects them; a plain sort pushes them to the end.
Dependencies
No dependencies beyond React.
Files
- lib/text-match.ts
FAQ
Is the Text match helper free?
Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.
How do I install Text match?
Run npx wingo-ui@latest add text-match. With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/text-match.json.
See also
Used in
More React hooks and utilities
Articles using this component
- React Combobox: Async Search Without Race Conditions
Component guides10 min read
- Romanian Address Format: The Rules and a React Form
Build it: screens and apps10 min read