npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

accent-folding

v2.7.0

Published

Accent-insensitive text matching with built-in search highlighting — the only library that returns original accented text with HTML markup around matches

Readme

accent-folding npm version npm downloads Bundle size GitHub package.json version Coverage Status

Live demo →

Searching "cafe" should find "café". Highlighting "lo" in "López" should show <b>Ló</b>pez — not stripped text.

accent-folding is the only library that solves both: accent-insensitive matching that returns the original accented string with HTML markup around matches — or raw index positions for React, React Native, PDF, canvas, and any environment where HTML strings don't belong. Zero dependencies. ~2.4 kB gzipped. Ships as dual ESM + CJS — works in Node ESM, CommonJS, Jest (no transform config), Next.js pages router, and any bundler.

Installation

Install with npm:

npm install accent-folding

or with pnpm:

pnpm install accent-folding

or with bun:

bun add accent-folding

or with yarn:

yarn add accent-folding

Module formats

The package ships as a dual ESM + CJS build. No configuration needed — your toolchain picks the right format automatically:

| Environment | Format used | | -------------------------------------------------------- | -------------------------- | | import / bundlers (Vite, webpack, Rollup) | ESM (dist/esm/index.js) | | require() / Jest (no transform) / Next.js pages router | CJS (dist/cjs/index.cjs) |

// ESM
import AccentFolding from 'accent-folding';

// CommonJS
const AccentFolding = require('accent-folding');

const af = new AccentFolding();
console.log(af.replace('café')); // → 'cafe'

TypeScript

Full type declarations are included. No @types/ package needed.

import AccentFolding, { type AccentMap, type MatchPosition } from 'accent-folding';

const af = new AccentFolding();

const replaced: string = af.replace('café');
const positions: MatchPosition[] = af.matchPositions('López', 'lo');
const highlighted: string = af.highlightMatch('López', 'lo');
const highlightedMark: string = af.highlightMatch('López', 'lo', 'mark');

// Custom accent map with typed argument
const customMap: AccentMap = { ö: 'oe', ü: 'ue' };
const afCustom = new AccentFolding(customMap);

// Type your own rendering helper
function renderParts(text: string, positions: MatchPosition[]) { ... }

Public Methods

matchPositions

Returns an array of { start, end } index pairs for every accent-insensitive match of fragment in str. end is exclusive — pass directly to str.slice(start, end).

import AccentFolding from 'accent-folding';

const af = new AccentFolding();

af.matchPositions('Fulanilo López', 'lo');
// → [{ start: 6, end: 8 }, { start: 9, end: 11 }]

af.matchPositions('Straße', 'ss');
// → [{ start: 4, end: 5 }]  ß treated as ss, position points back to ß

af.matchPositions('Hello World', 'xyz');
// → []

This is the framework-agnostic primitive. Use it when you need to control rendering yourself — React without dangerouslySetInnerHTML, React Native, PDF generation, canvas, terminal ANSI codes, and so on.

highlightMatch

Matches a search fragment against a string, ignoring accents, and wraps each match in an HTML tag — returning the original accented characters intact.

  • Accent-insensitive matching
  • Returns original accented text with HTML markup around matches
  • Customizable highlight tag (default: <b>, use strong, mark, span, etc.)
  • Handles various Unicode characters, including fullwidth ASCII

Note: highlightMatch returns an HTML string and does not escape its inputs. Only inject the output into the DOM when str comes from trusted, app-controlled data — not from untrusted user input.

import AccentFolding from 'accent-folding';

const af = new AccentFolding();

af.highlightMatch('Fulanilo López', 'lo'); // --> "Fulani<b>lo</b> <b>Ló</b>pez"

Use the third argument to specify the wrapping HTML tag:

af.highlightMatch('Fulanilo López', 'lo', 'strong'); // --> "Fulani<strong>lo</strong> <strong>Ló</strong>pez"
af.highlightMatch('Fulanilo López', 'lo', 'mark'); // --> "Fulani<mark>lo</mark> <mark>Ló</mark>pez"

replace

Replaces accented characters in a string with their unaccented equivalents.

  • Handles various Unicode characters, including fullwidth ASCII
  • Preserves original string formatting in the output
import AccentFolding from 'accent-folding';

const af = new AccentFolding();

af.replace('Fulanilo López'); // --> "Fulanilo Lopez"

Extending and Overriding the Accent Map

The AccentFolding class allows you to extend or override the default accent map by providing a custom map to the constructor.

Extending the Accent Map

To extend the accent map with new mappings, pass an object with the new mappings to the constructor. For example:

import AccentFolding from 'accent-folding';

const customAccentMap = { ö: 'oe', '✝': 't' };
const accentFolder = new AccentFolding(customAccentMap);

console.log(accentFolder.replace('Föhn')); // Outputs: Foehn
console.log(accentFolder.replace('✝illa')); // Outputs: tilla

Real-World Examples

CommonJS (Jest, Next.js pages router)

const AccentFolding = require('accent-folding');

const af = new AccentFolding();
const names = ['López', 'Müller', 'Björk', 'Ñoño'];

// Filter and highlight — identical API to ESM
const results = names
	.filter((name) =>
		af.replace(name).toLowerCase().includes(af.replace('lo').toLowerCase())
	)
	.map((name) => af.highlightMatch(name, 'lo'));

console.log(results); // ['<b>Ló</b>pez']

Plain JS (browser)

import AccentFolding from 'accent-folding';

const af = new AccentFolding();
const names = ['López', 'Müller', 'Björk', 'Ñoño', 'García', 'Renée'];

const input = document.querySelector('#search');
const list = document.querySelector('#results');

input.addEventListener('input', () => {
	const query = input.value.trim();
	const matches = query
		? names.filter((name) =>
				af.replace(name).toLowerCase().includes(af.replace(query).toLowerCase())
			)
		: names;

	list.innerHTML = matches
		.map((name) => `<li>${query ? af.highlightMatch(name, query) : name}</li>`)
		.join('');
});

React — with matchPositions (recommended)

Use matchPositions to build real React elements — no dangerouslySetInnerHTML, no ESLint warnings, works with concurrent mode and React Server Components.

import { useState } from 'react';
import AccentFolding from 'accent-folding';

const af = new AccentFolding();
const names = ['López', 'Müller', 'Björk', 'Ñoño', 'García', 'Renée'];

function Highlight({ text, query }) {
	const positions = af.matchPositions(text, query);
	if (!positions.length) return <span>{text}</span>;

	const parts = [];
	let last = 0;
	for (const { start, end } of positions) {
		if (start > last) parts.push(text.slice(last, start));
		parts.push(
			<mark key={start} className="bg-yellow-200 rounded px-0.5 font-semibold">
				{text.slice(start, end)}
			</mark>
		);
		last = end;
	}
	parts.push(text.slice(last));
	return <span>{parts}</span>;
}

export default function AccentSearch() {
	const [query, setQuery] = useState('');

	const matches = query
		? names.filter((name) =>
				af.replace(name).toLowerCase().includes(af.replace(query).toLowerCase())
			)
		: names;

	return (
		<div>
			<input
				value={query}
				onChange={(e) => setQuery(e.target.value)}
				placeholder="Search..."
			/>
			<ul>
				{matches.map((name) => (
					<li key={name}>
						<Highlight text={name} query={query} />
					</li>
				))}
			</ul>
		</div>
	);
}

React Native

highlightMatch is useless in React Native — there is no DOM. matchPositions is the only way to highlight accented text in a native app.

import { Text, StyleSheet } from 'react-native';
import AccentFolding from 'accent-folding';

const af = new AccentFolding();

function Highlight({ text, query }) {
	const positions = af.matchPositions(text, query);
	if (!positions.length) return <Text>{text}</Text>;

	const parts = [];
	let last = 0;
	for (const { start, end } of positions) {
		if (start > last)
			parts.push(<Text key={`t-${last}`}>{text.slice(last, start)}</Text>);
		parts.push(
			<Text key={`h-${start}`} style={styles.highlight}>
				{text.slice(start, end)}
			</Text>
		);
		last = end;
	}
	parts.push(<Text key="tail">{text.slice(last)}</Text>);
	return <Text>{parts}</Text>;
}

const styles = StyleSheet.create({
	highlight: { fontWeight: 'bold', backgroundColor: '#fef08a' },
});

A full React + TypeScript demo (Vite, typed AccentMap, search highlight, custom map showcase) is available in demo/. Run it with:

cd demo && pnpm dev

Requirements

Node.js ≥18

v1 exported a single function (no class). If you need require() support with the current API, see the Module formats section above — v2+ ships dual ESM + CJS natively.

Install v1:

npm install accent-folding@1

Example:

const accentFoldedHighlight = require('accent-folding');

accentFoldedHighlight('Fulanilo López', 'lo'); // --> "Fulani<b>lo</b> <b>Ló</b>pez"
accentFoldedHighlight('Fulanilo López', 'lo', 'strong'); // --> "Fulani<strong>lo</strong> <strong>Ló</strong>pez"

Migrating from latinize

latinize was archived in November 2024. Here's how to migrate to accent-folding:

- import latinize from 'latinize';
+ import AccentFolding from 'accent-folding';
+ const af = new AccentFolding();

- const result = latinize('Ñoño García');
+ const result = af.replace('Ñoño García');

- const filtered = names.filter(n => latinize(n).toLowerCase().includes(latinize(query).toLowerCase()));
+ const filtered = names.filter(n => af.replace(n).toLowerCase().includes(af.replace(query).toLowerCase()));

// New: highlight matches in results (no latinize equivalent)
+ const highlighted = af.highlightMatch('López', 'lo'); // → '<b>Ló</b>pez'

How they compare

| | latinize | accent-folding | | --------------------------- | ---------------------------------- | ------------------------------------------------------------------------------- | | Status | ❌ Archived Nov 2024 | ✅ Actively maintained | | highlightMatch() | ❌ No | ✅ Unique — wraps matches in HTML while preserving original accented characters | | matchPositions() | ❌ No | ✅ Framework-agnostic — works in React, React Native, PDF, canvas, terminal | | NFC/NFD normalization | ❌ NFD input produces wrong output | ✅ Handles both automatically | | Result caching | ❌ No | ✅ Yes — repeated calls with the same input are cached | | Test coverage | ❌ Not reported | ✅ 100% line + branch coverage | | TypeScript | ⚠️ Added in v2 (archived) | ✅ Full types, no @types/ needed | | Latin script coverage | ✅ ~1,000 entries | ✅ ~700 entries (same practical coverage for Western European text) | | Cyrillic / IPA / subscripts | ✅ Yes | ❌ No — Latin script only | | Bundle size | ~6 kB gzipped | ~2.7 kB gzipped |

Honest caveat: latinize covers ~1,000 character mappings including Cyrillic, IPA phonetic symbols, and subscripts. accent-folding focuses on Latin script only (~700 entries). For typical search/autocomplete over Western European names and text the coverage is identical — but if you need to fold Russian, Greek, or IPA, transliteration is the right choice.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Development requires Node.js ≥22 (ESLint v10 constraint). The published package supports Node.js ≥18 for consumers.

Credits

The initial idea came from the article Accent Folding for Auto-Complete by Carlos Bueno on A List Apart (February 2010). This library has since grown beyond that original concept, but the credit belongs there.